From f3429a1b28ce600f3b2fe90e6809c16ab985d90a Mon Sep 17 00:00:00 2001 From: delcroip Date: Wed, 30 Oct 2024 13:58:04 +0100 Subject: [PATCH] Fix contract + test insureepolicy + flake --- README.md | 18 +- contract/__init__.py | 3 +- contract/admin.py | 3 +- contract/apps.py | 3 +- contract/config.py | 161 +--- contract/gql/__init__.py | 2 +- contract/gql/gql_mutations/__init__.py | 1 - .../contract_details_mutations.py | 39 +- .../gql/gql_mutations/contract_mutations.py | 111 ++- contract/gql/gql_mutations/input_types.py | 5 +- contract/gql/gql_mutations/mutations.py | 221 +++-- contract/gql/gql_types.py | 38 +- ...contributionplandetails_amount_and_more.py | 48 + contract/models.py | 135 ++- contract/schema.py | 110 ++- contract/services.py | 887 ++++++++++-------- contract/signals.py | 210 +++-- contract/tasks.py | 25 +- contract/tests/__init__.py | 4 - contract/tests/gql_tests/__init__.py | 2 - contract/tests/helpers.py | 86 +- contract/tests/helpers_tests.py | 99 -- contract/tests/services/__init__.py | 1 - contract/tests/test_helpers.py | 158 ++++ ...tests.py => test_mutation_create_tests.py} | 226 +++-- .../services_tests.py => test_services.py} | 306 +++--- .../query_tests.py => tests_gql_query.py} | 323 ++++--- contract/urls.py | 3 +- contract/utils.py | 32 +- contract/views.py | 2 +- setup.py | 60 +- 31 files changed, 1957 insertions(+), 1365 deletions(-) create mode 100644 contract/migrations/0023_contractcontributionplandetails_amount_and_more.py delete mode 100644 contract/tests/gql_tests/__init__.py delete mode 100644 contract/tests/helpers_tests.py delete mode 100644 contract/tests/services/__init__.py create mode 100644 contract/tests/test_helpers.py rename contract/tests/{gql_tests/mutation_create_tests.py => test_mutation_create_tests.py} (62%) rename contract/tests/{services/services_tests.py => test_services.py} (64%) rename contract/tests/{gql_tests/query_tests.py => tests_gql_query.py} (69%) diff --git a/README.md b/README.md index 2eea243..f803fd2 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,12 @@ It is dedicated to be deployed as a module of [openimis-be_py](https://github.co * tblContractContributionPlanDetails > ContractContributionPlanDetails ## Listened Django Signals -- post_save - Payment: handles service activate_contracted_policies - only when payment +- post_save - Payment: handles service activate_contracted_policies - only when payment is related to the contract/contracts. (it is verified by this post save) Another payments are omitted in processing. ## GraphQl Queries -* contract +* contract * contractDetails * contractContributionPlanDetails @@ -35,7 +35,7 @@ Another payments are omitted in processing. ## Additional mutation log helper in Create graphQl mutations - ObjectMutation from core - "ObjectMutation" from openimis-be-core_py - models.py - "object_mutated" method allows the creation of an object to update the xxxMutation easily. -- dedicated for createContract and createContractDetails graphQl mutations +- dedicated for createContract and createContractDetails graphQl mutations - more info about it and how it was implemented here in Contract module in models.py in class ObjectMutation in docs string ## Services @@ -48,10 +48,10 @@ Another payments are omitted in processing. - renew - delete - get_negative_amount_amendment - - terminate_contract + - terminate_contract - ContractDetails - - update_from_ph_insuree - - ph_insuree_to_contract_details + - get_details_from_ph_insuree + - ph_insuree_to_contract_details - ContractContributionPlanDetails - CRUD services, replace - create_ccpd (ccpd - acronym of contract contribution plan details) - contract_valuation @@ -90,8 +90,8 @@ Another payments are omitted in processing. ## bulk operations - required configuration -- for 'bulk approve contract' and 'bulk counter contract' graphQL mutations +- for 'bulk approve contract' and 'bulk counter contract' graphQL mutations - running rabbitmq docker image -- running celery within "{imis_directory}/openimis-be_py/venv/bin/celery": +- running celery within "{imis_directory}/openimis-be_py/venv/bin/celery": `-A openIMIS worker --loglevel=DEBUG --without-gossip --without-mingle --without-heartbeat -Ofair` -- without this required steps you won't be able to bulk counter/approve contract +- without this required steps you won't be able to bulk counter/approve contract diff --git a/contract/__init__.py b/contract/__init__.py index a063b88..f5f8d35 100644 --- a/contract/__init__.py +++ b/contract/__init__.py @@ -1,2 +1 @@ - -default_app_config = 'contract.apps.ContractConfig' \ No newline at end of file +default_app_config = "contract.apps.ContractConfig" diff --git a/contract/admin.py b/contract/admin.py index 8c38f3f..5d28852 100644 --- a/contract/admin.py +++ b/contract/admin.py @@ -1,3 +1,2 @@ -from django.contrib import admin - +# from django.contrib import admin # Register your models here. diff --git a/contract/apps.py b/contract/apps.py index 4f1b1e7..3ca7284 100644 --- a/contract/apps.py +++ b/contract/apps.py @@ -1,6 +1,5 @@ from django.apps import AppConfig - MODULE_NAME = "contract" @@ -59,6 +58,6 @@ def __load_config(self, cfg): def ready(self): from core.models import ModuleConfiguration + cfg = ModuleConfiguration.get_or_default(MODULE_NAME, DEFAULT_CFG) self.__load_config(cfg) - import contract.signals \ No newline at end of file diff --git a/contract/config.py b/contract/config.py index 8dcf880..baf24c7 100644 --- a/contract/config.py +++ b/contract/config.py @@ -2,147 +2,68 @@ "ContractState": [ { "value": "1", - "label": - { - "fr": "Demande d'information", - "en": "Request for information" - } + "label": {"fr": "Demande d'information", "en": "Request for information"}, }, - { - "value": "2", - "label": - { - "fr": "Brouillon", - "en": "Draft" - } - }, - { - "value": "3", - "label": - { - "fr": "Offre", - "en": "offer" - } - }, - { - "value": "4", - "label": - { - "fr": "En negociation", - "en": "Negotiable" - - } - }, - { - "value": "5", - "label": - { - "fr": "Apprové", - "en": "executable" - - } - }, - { - "value": "6", - "label": - { - "fr": "addendum", - "en": "addendum" - } - }, - { - "value": "7", - "label": - { - "fr": "En cours", - "en": "effective" - } - }, - { - "value": "8", - "label": - { - "fr": "Appliqué", - "en": "executed" - - } - }, - { - "value": "9", - "label": - { - "fr": "Suspendu", - "en": "Disputed" - } - }, - { - "value": "10", - "label": - { - "fr": "Terminé", - "en": "terminated" - } - }, - { - "value": "11", - "label": - { - "fr": "révision demandé", - "en": "counter" - } - }] + {"value": "2", "label": {"fr": "Brouillon", "en": "Draft"}}, + {"value": "3", "label": {"fr": "Offre", "en": "offer"}}, + {"value": "4", "label": {"fr": "En negociation", "en": "Negotiable"}}, + {"value": "5", "label": {"fr": "Apprové", "en": "executable"}}, + {"value": "6", "label": {"fr": "addendum", "en": "addendum"}}, + {"value": "7", "label": {"fr": "En cours", "en": "effective"}}, + {"value": "8", "label": {"fr": "Appliqué", "en": "executed"}}, + {"value": "9", "label": {"fr": "Suspendu", "en": "Disputed"}}, + {"value": "10", "label": {"fr": "Terminé", "en": "terminated"}}, + {"value": "11", "label": {"fr": "révision demandé", "en": "counter"}}, + ] } -def get_message_approved_contract(code, name, contact_name, due_amount, payment_reference, language='en'): +def get_message_approved_contract( + code, name, contact_name, due_amount, payment_reference, language="en" +): message_payment_notification = { - "payment_notification": - {"en": - F""" - Dear {contact_name} - + "payment_notification": { + "en": f""" + Dear {contact_name} + The contract {code} - {name} was approved. - Please proceed to the payment of {due_amount} with the reference {payment_reference}. - - Best regards, - """ - , - "fr": - F""" + Please proceed to the payment of {due_amount} with the reference {payment_reference}. + + Best regards, + """, + "fr": f""" Monsieur, Madame {contact_name} - + le contract {code} - {name} à été apprové. Veuillez faire un paiement de < CONTRACT - DUEAMOUNT > avec la référence {payment_reference}. - - Meilleurs Salutations - """ - } + + Meilleurs Salutations + """, + } } return message_payment_notification["payment_notification"][language] -def get_message_counter_contract(code, name, contact_name, language='en'): +def get_message_counter_contract(code, name, contact_name, language="en"): message_payment_notification = { - "payment_notification": - {"en": - F""" - Dear {contact_name} + "payment_notification": { + "en": f""" + Dear {contact_name} The contract {code} - {name} was counter. - Please proceed recheck the information and correct the issues, in case of questions please check contact us. + Please proceed recheck the information and correct the issues, + in case of questions please check contact us. - Best regards, - """ - , - "fr": - F""" + Best regards, + """, + "fr": f""" Monsieur, Madame {contact_name} le contract {code} - {name} à été contré. Veuillez verifier les information saisie, en cas de question veuillez nous contacter. - Meilleurs Salutations - """ - } + Meilleurs Salutations + """, + } } return message_payment_notification["payment_notification"][language] diff --git a/contract/gql/__init__.py b/contract/gql/__init__.py index b7d19ef..7da6850 100644 --- a/contract/gql/__init__.py +++ b/contract/gql/__init__.py @@ -1 +1 @@ -from .gql_types import * \ No newline at end of file +# from .gql_types import * diff --git a/contract/gql/gql_mutations/__init__.py b/contract/gql/gql_mutations/__init__.py index 06f3ca5..e69de29 100644 --- a/contract/gql/gql_mutations/__init__.py +++ b/contract/gql/gql_mutations/__init__.py @@ -1 +0,0 @@ -from .input_types import * \ No newline at end of file diff --git a/contract/gql/gql_mutations/contract_details_mutations.py b/contract/gql/gql_mutations/contract_details_mutations.py index 9705068..8bee890 100644 --- a/contract/gql/gql_mutations/contract_details_mutations.py +++ b/contract/gql/gql_mutations/contract_details_mutations.py @@ -1,12 +1,21 @@ from core.gql.gql_mutations import DeleteInputType -from core.gql.gql_mutations.base_mutation import BaseMutation, BaseDeleteMutation, \ - BaseHistoryModelCreateMutationMixin, BaseHistoryModelUpdateMutationMixin, \ - BaseHistoryModelDeleteMutationMixin -from .mutations import ContractDetailsFromPHInsureeMutationMixin -from contract.gql.gql_mutations import ContractDetailsCreateInputType, ContractDetailsUpdateInputType, \ - ContractDetailsCreateFromInsureeInputType +from core.gql.gql_mutations.base_mutation import ( + BaseDeleteMutation, + BaseHistoryModelCreateMutationMixin, + BaseHistoryModelDeleteMutationMixin, + BaseHistoryModelUpdateMutationMixin, + BaseMutation, +) + +from contract.gql.gql_mutations.input_types import ( + ContractDetailsCreateFromInsureeInputType, + ContractDetailsCreateInputType, + ContractDetailsUpdateInputType, +) from contract.models import ContractDetails, ContractDetailsMutation +from .mutations import ContractDetailsFromPHInsureeMutationMixin + class CreateContractDetailsMutation(BaseHistoryModelCreateMutationMixin, BaseMutation): _mutation_class = "ContractDetailsMutation" @@ -17,11 +26,13 @@ class CreateContractDetailsMutation(BaseHistoryModelCreateMutationMixin, BaseMut def _mutate(cls, user, **data): client_mutation_id = data.get("client_mutation_id") if "client_mutation_id" in data: - data.pop('client_mutation_id') + data.pop("client_mutation_id") if "client_mutation_label" in data: - data.pop('client_mutation_label') + data.pop("client_mutation_label") contract_detail = cls.create_object(user=user, object_data=data) - ContractDetailsMutation.object_mutated(user, client_mutation_id=client_mutation_id, contract_detail=contract_detail) + ContractDetailsMutation.object_mutated( + user, client_mutation_id=client_mutation_id, contract_detail=contract_detail + ) return None class Input(ContractDetailsCreateInputType): @@ -37,7 +48,9 @@ class Input(ContractDetailsUpdateInputType): pass -class DeleteContractDetailsMutation(BaseHistoryModelDeleteMutationMixin, BaseDeleteMutation): +class DeleteContractDetailsMutation( + BaseHistoryModelDeleteMutationMixin, BaseDeleteMutation +): _mutation_class = "ContractDetailsMutation" _mutation_module = "contract" _model = ContractDetails @@ -46,10 +59,12 @@ class Input(DeleteInputType): pass -class CreateContractDetailByPolicyHolderInsureeMutation(ContractDetailsFromPHInsureeMutationMixin, BaseMutation): +class CreateContractDetailByPolicyHolderInsureeMutation( + ContractDetailsFromPHInsureeMutationMixin, BaseMutation +): _mutation_class = "CreateContractDetailByPolicyHolderInsureetMutation" _mutation_module = "contract" _model = ContractDetails class Input(ContractDetailsCreateFromInsureeInputType): - pass \ No newline at end of file + pass diff --git a/contract/gql/gql_mutations/contract_mutations.py b/contract/gql/gql_mutations/contract_mutations.py index 64234e1..bb303ee 100644 --- a/contract/gql/gql_mutations/contract_mutations.py +++ b/contract/gql/gql_mutations/contract_mutations.py @@ -1,19 +1,43 @@ -from core.gql.gql_mutations import DeleteInputType, mutation_on_uuids_from_filter_business_model -from core.gql.gql_mutations.base_mutation import BaseMutation, BaseDeleteMutation -from .mutations import ContractCreateMutationMixin, ContractUpdateMutationMixin, \ - ContractDeleteMutationMixin, ContractSubmitMutationMixin, \ - ContractApproveMutationMixin, ContractCounterMutationMixin, \ - ContractAmendMutationMixin, ContractRenewMutationMixin, ContractCreateInvoiceMutationMixin -from contract.models import Contract -from contract.gql.gql_types import ContractGQLType -from contract.gql.gql_mutations.input_types import ContractCreateInputType, ContractUpdateInputType, \ - ContractSubmitInputType, ContractApproveInputType, ContractCounterInputType, \ - ContractApproveBulkInputType, ContractAmendInputType, ContractRenewInputType, \ - ContractCounterBulkInputType, ContractCreateInvoiceBulkInputType -from contract.tasks import approve_contracts, counter_contracts, create_invoice_from_contracts -from contract.exceptions import CeleryWorkerError +from core.gql.gql_mutations import ( + DeleteInputType, + mutation_on_uuids_from_filter_business_model, +) +from core.gql.gql_mutations.base_mutation import BaseDeleteMutation, BaseMutation from kombu.exceptions import OperationalError +from contract.exceptions import CeleryWorkerError +from contract.gql.gql_mutations.input_types import ( + ContractAmendInputType, + ContractApproveBulkInputType, + ContractApproveInputType, + ContractCounterBulkInputType, + ContractCounterInputType, + ContractCreateInputType, + ContractCreateInvoiceBulkInputType, + ContractRenewInputType, + ContractSubmitInputType, + ContractUpdateInputType, +) +from contract.gql.gql_types import ContractGQLType +from contract.models import Contract +from contract.tasks import ( + approve_contracts, + counter_contracts, + create_invoice_from_contracts, +) + +from .mutations import ( + ContractAmendMutationMixin, + ContractApproveMutationMixin, + ContractCounterMutationMixin, + ContractCreateInvoiceMutationMixin, + ContractCreateMutationMixin, + ContractDeleteMutationMixin, + ContractRenewMutationMixin, + ContractSubmitMutationMixin, + ContractUpdateMutationMixin, +) + class CreateContractMutation(ContractCreateMutationMixin, BaseMutation): _mutation_class = "CreateContractMutation" @@ -66,13 +90,15 @@ class ApproveContractBulkMutation(ContractApproveMutationMixin, BaseMutation): _model = Contract @classmethod - @mutation_on_uuids_from_filter_business_model(Contract, ContractGQLType, 'extended_filters', {}) + @mutation_on_uuids_from_filter_business_model( + Contract, ContractGQLType, "extended_filters", {} + ) def async_mutate(cls, user, **data): error_message = None if "client_mutation_id" in data: - data.pop('client_mutation_id') + data.pop("client_mutation_id") if "client_mutation_label" in data: - data.pop('client_mutation_label') + data.pop("client_mutation_label") if "contract_uuids" in data or "uuids" in data: error_message = cls.approve_contracts(user=user, contracts=data) return error_message @@ -80,21 +106,28 @@ def async_mutate(cls, user, **data): def _check_celery_status(cls): try: from openIMIS.celery import app as celery_app + connection = celery_app.broker_connection().ensure_connection(max_retries=3) if not connection: - raise CeleryWorkerError("Celery worker not found. Please, contact your system administrator.") + raise CeleryWorkerError( + "Celery worker not found. Please, contact your system administrator." + ) except (IOError, OperationalError) as e: raise CeleryWorkerError( - F"Celery connection has failed. Error: {e} \n Please, contact your system administrator.") + f"Celery connection has failed. Error: {e} \n Please, contact your system administrator." + ) + @classmethod def approve_contracts(cls, user, contracts): if "uuids" in contracts: contracts["uuids"] = list(contracts["uuids"].values_list("id", flat=True)) - return approve_contracts(user_id=f'{user.id}', contracts=contracts["uuids"]) + return approve_contracts(user_id=f"{user.id}", contracts=contracts["uuids"]) else: if "contract_uuids" in contracts: - return approve_contracts(user_id=f'{user.id}', contracts=contracts["contract_uuids"]) + return approve_contracts( + user_id=f"{user.id}", contracts=contracts["contract_uuids"] + ) class Input(ContractApproveBulkInputType): pass @@ -109,18 +142,22 @@ class Input(ContractCounterInputType): pass -class ContractCreateInvoiceBulkMutation(ContractCreateInvoiceMutationMixin, BaseMutation): +class ContractCreateInvoiceBulkMutation( + ContractCreateInvoiceMutationMixin, BaseMutation +): _mutation_class = "ContractCreateInvoiceBulkMutation" _mutation_module = "contract" _model = Contract @classmethod - @mutation_on_uuids_from_filter_business_model(Contract, ContractGQLType, 'extended_filters', {}) + @mutation_on_uuids_from_filter_business_model( + Contract, ContractGQLType, "extended_filters", {} + ) def async_mutate(cls, user, **data): if "client_mutation_id" in data: - data.pop('client_mutation_id') + data.pop("client_mutation_id") if "client_mutation_label" in data: - data.pop('client_mutation_label') + data.pop("client_mutation_label") if "contract_uuids" in data or "uuids" in data: cls.create_contract_invoice(user=user, contracts=data) return None @@ -129,10 +166,14 @@ def async_mutate(cls, user, **data): def create_contract_invoice(cls, user, contracts): if "uuids" in contracts: contracts["uuids"] = list(contracts["uuids"].values_list("id", flat=True)) - return create_invoice_from_contracts(user_id=f'{user.id}', contracts=contracts["uuids"]) + return create_invoice_from_contracts( + user_id=f"{user.id}", contracts=contracts["uuids"] + ) else: if "contract_uuids" in contracts: - return create_invoice_from_contracts(user_id=f'{user.id}', contracts=contracts["contract_uuids"]) + return create_invoice_from_contracts( + user_id=f"{user.id}", contracts=contracts["contract_uuids"] + ) class Input(ContractCreateInvoiceBulkInputType): pass @@ -144,12 +185,14 @@ class CounterContractBulkMutation(ContractCounterMutationMixin, BaseMutation): _model = Contract @classmethod - @mutation_on_uuids_from_filter_business_model(Contract, ContractGQLType, 'extended_filters', {}) + @mutation_on_uuids_from_filter_business_model( + Contract, ContractGQLType, "extended_filters", {} + ) def async_mutate(cls, user, **data): if "client_mutation_id" in data: - data.pop('client_mutation_id') + data.pop("client_mutation_id") if "client_mutation_label" in data: - data.pop('client_mutation_label') + data.pop("client_mutation_label") if "contract_uuids" in data or "uuids" in data: cls.counter_contracts(user=user, contracts=data) return None @@ -158,10 +201,12 @@ def async_mutate(cls, user, **data): def counter_contracts(cls, user, contracts): if "uuids" in contracts: contracts["uuids"] = list(contracts["uuids"].values_list("id", flat=True)) - return counter_contracts(user_id=f'{user.id}', contracts=contracts["uuids"]) + return counter_contracts(user_id=f"{user.id}", contracts=contracts["uuids"]) else: if "contract_uuids" in contracts: - return counter_contracts(user_id=f'{user.id}', contracts=contracts["contract_uuids"]) + return counter_contracts( + user_id=f"{user.id}", contracts=contracts["contract_uuids"] + ) class Input(ContractCounterBulkInputType): pass @@ -182,4 +227,4 @@ class RenewContractMutation(ContractRenewMutationMixin, BaseMutation): _model = Contract class Input(ContractRenewInputType): - pass \ No newline at end of file + pass diff --git a/contract/gql/gql_mutations/input_types.py b/contract/gql/gql_mutations/input_types.py index 6d8c799..b65c12a 100644 --- a/contract/gql/gql_mutations/input_types.py +++ b/contract/gql/gql_mutations/input_types.py @@ -1,6 +1,5 @@ import graphene - -from core.schema import OpenIMISMutation, TinyInt +from core.schema import OpenIMISMutation class ContractCreateInputType(OpenIMISMutation.Input): @@ -105,4 +104,4 @@ class ContractDetailsUpdateInputType(OpenIMISMutation.Input): class ContractDetailsCreateFromInsureeInputType(OpenIMISMutation.Input): contract_id = graphene.UUID(required=True) - policy_holder_insuree_id = graphene.UUID(required=True) \ No newline at end of file + policy_holder_insuree_id = graphene.UUID(required=True) diff --git a/contract/gql/gql_mutations/mutations.py b/contract/gql/gql_mutations/mutations.py index 315462d..1649d93 100644 --- a/contract/gql/gql_mutations/mutations.py +++ b/contract/gql/gql_mutations/mutations.py @@ -1,12 +1,18 @@ -from core import TimeUtils -from core.schema import OpenIMISMutation from core.gql.gql_mutations import ObjectNotExistException -from contract.services import Contract as ContractService, \ - ContractDetails as ContractDetailsService, ContractToInvoiceService -from contract.models import Contract, ContractMutation -from contract.apps import ContractConfig from django.contrib.auth.models import AnonymousUser from django.core.exceptions import ValidationError +from django.forms.models import model_to_dict +from django.utils.translation import gettext as _ + +from contract.apps import ContractConfig +from contract.models import Contract, ContractMutation +from contract.services import ( + Contract as ContractService, + ContractDetails as ContractDetailsService, + ContractToInvoiceService, + _output_result_success, + _output_exception +) class ContractCreateMutationMixin: @@ -17,20 +23,26 @@ def _model(self): @classmethod def _validate_mutation(cls, user, **data): - if type(user) is AnonymousUser or not user.id or not user.has_perms(ContractConfig.gql_mutation_create_contract_perms): + if ( + type(user) is AnonymousUser + or not user.id + or not user.has_perms(ContractConfig.gql_mutation_create_contract_perms) + ): raise ValidationError("mutation.authentication_required") @classmethod def _mutate(cls, user, **data): client_mutation_id = data.get("client_mutation_id") if "client_mutation_id" in data: - data.pop('client_mutation_id') + data.pop("client_mutation_id") if "client_mutation_label" in data: - data.pop('client_mutation_label') + data.pop("client_mutation_label") output = cls.create_contract(user=user, contract=data) if output["success"]: contract = Contract.objects.get(id=output["data"]["id"]) - ContractMutation.object_mutated(user, client_mutation_id=client_mutation_id, contract=contract) + ContractMutation.object_mutated( + user, client_mutation_id=client_mutation_id, contract=contract + ) return None else: return f"Error! - {output['message']}: {output['detail']}" @@ -50,17 +62,25 @@ def _model(self): @classmethod def _validate_mutation(cls, user, **data): - if type(user) is AnonymousUser or not user.id or not user.has_perms(ContractConfig.gql_mutation_update_contract_perms): + if ( + type(user) is AnonymousUser + or not user.id + or not user.has_perms(ContractConfig.gql_mutation_update_contract_perms) + ): raise ValidationError("mutation.authentication_required") @classmethod def _mutate(cls, user, **data): if "client_mutation_id" in data: - data.pop('client_mutation_id') + data.pop("client_mutation_id") if "client_mutation_label" in data: - data.pop('client_mutation_label') + data.pop("client_mutation_label") output = cls.update_contract(user=user, contract=data) - return None if output["success"] else f"Error! - {output['message']}: {output['detail']}" + return ( + None + if output["success"] + else f"Error! - {output['message']}: {output['detail']}" + ) @classmethod def update_contract(cls, user, contract): @@ -90,7 +110,11 @@ def _validate_user(cls, user): @classmethod def _mutate(cls, user, uuid): output = cls.delete_contract(user=user, contract={"id": uuid}) - return None if output["success"] else f"Error! - {output['message']}: {output['detail']}" + return ( + None + if output["success"] + else f"Error! - {output['message']}: {output['detail']}" + ) @classmethod def delete_contract(cls, user, contract): @@ -107,17 +131,25 @@ def _model(self): @classmethod def _validate_mutation(cls, user, **data): - if type(user) is AnonymousUser or not user.id or not user.has_perms(ContractConfig.gql_mutation_submit_contract_perms): + if ( + type(user) is AnonymousUser + or not user.id + or not user.has_perms(ContractConfig.gql_mutation_submit_contract_perms) + ): raise ValidationError("mutation.authentication_required") @classmethod def _mutate(cls, user, **data): if "client_mutation_id" in data: - data.pop('client_mutation_id') + data.pop("client_mutation_id") if "client_mutation_label" in data: - data.pop('client_mutation_label') + data.pop("client_mutation_label") output = cls.submit_contract(user=user, contract=data) - return None if output["success"] else f"Error! - {output['message']}: {output['detail']}" + return ( + None + if output["success"] + else f"Error! - {output['message']}: {output['detail']}" + ) @classmethod def submit_contract(cls, user, contract): @@ -134,17 +166,27 @@ def _model(self): @classmethod def _validate_mutation(cls, user, **data): - if type(user) is AnonymousUser or not user.id or not user.has_perms(ContractConfig.gql_mutation_approve_ask_for_change_contract_perms): + if ( + type(user) is AnonymousUser + or not user.id + or not user.has_perms( + ContractConfig.gql_mutation_approve_ask_for_change_contract_perms + ) + ): raise ValidationError("mutation.authentication_required") @classmethod def _mutate(cls, user, **data): if "client_mutation_id" in data: - data.pop('client_mutation_id') + data.pop("client_mutation_id") if "client_mutation_label" in data: - data.pop('client_mutation_label') + data.pop("client_mutation_label") output = cls.approve_contract(user=user, contract=data) - return None if output["success"] else f"Error! - {output['message']}: {output['detail']}" + return ( + None + if output["success"] + else f"Error! - {output['message']}: {output['detail']}" + ) @classmethod def approve_contract(cls, user, contract): @@ -161,17 +203,27 @@ def _model(self): @classmethod def _validate_mutation(cls, user, **data): - if type(user) is AnonymousUser or not user.id or not user.has_perms(ContractConfig.gql_mutation_approve_ask_for_change_contract_perms): + if ( + type(user) is AnonymousUser + or not user.id + or not user.has_perms( + ContractConfig.gql_mutation_approve_ask_for_change_contract_perms + ) + ): raise ValidationError("mutation.authentication_required") @classmethod def _mutate(cls, user, **data): if "client_mutation_id" in data: - data.pop('client_mutation_id') + data.pop("client_mutation_id") if "client_mutation_label" in data: - data.pop('client_mutation_label') + data.pop("client_mutation_label") output = cls.counter_contract(user=user, contract=data) - return None if output["success"] else f"Error! - {output['message']}: {output['detail']}" + return ( + None + if output["success"] + else f"Error! - {output['message']}: {output['detail']}" + ) @classmethod def counter_contract(cls, user, contract): @@ -188,20 +240,26 @@ def _model(self): @classmethod def _validate_mutation(cls, user, **data): - if type(user) is AnonymousUser or not user.id or not user.has_perms(ContractConfig.gql_mutation_amend_contract_perms): + if ( + type(user) is AnonymousUser + or not user.id + or not user.has_perms(ContractConfig.gql_mutation_amend_contract_perms) + ): raise ValidationError("mutation.authentication_required") @classmethod def _mutate(cls, user, **data): client_mutation_id = data.get("client_mutation_id") if "client_mutation_id" in data: - data.pop('client_mutation_id') + data.pop("client_mutation_id") if "client_mutation_label" in data: - data.pop('client_mutation_label') + data.pop("client_mutation_label") output = cls.amend_contract(user=user, contract=data) if output["success"]: contract = Contract.objects.get(id=output["data"]["id"]) - ContractMutation.object_mutated(user, client_mutation_id=client_mutation_id, contract=contract) + ContractMutation.object_mutated( + user, client_mutation_id=client_mutation_id, contract=contract + ) return None else: return f"Error! - {output['message']}: {output['detail']}" @@ -221,20 +279,26 @@ def _model(self): @classmethod def _validate_mutation(cls, user, **data): - if type(user) is AnonymousUser or not user.id or not user.has_perms(ContractConfig.gql_mutation_renew_contract_perms): + if ( + type(user) is AnonymousUser + or not user.id + or not user.has_perms(ContractConfig.gql_mutation_renew_contract_perms) + ): raise ValidationError("mutation.authentication_required") @classmethod def _mutate(cls, user, **data): client_mutation_id = data.get("client_mutation_id") if "client_mutation_id" in data: - data.pop('client_mutation_id') + data.pop("client_mutation_id") if "client_mutation_label" in data: - data.pop('client_mutation_label') + data.pop("client_mutation_label") output = cls.renew_contract(user=user, contract=data) if output["success"]: contract = Contract.objects.get(id=output["data"]["id"]) - ContractMutation.object_mutated(user, client_mutation_id=client_mutation_id, contract=contract) + ContractMutation.object_mutated( + user, client_mutation_id=client_mutation_id, contract=contract + ) return None else: return f"Error! - {output['message']}: {output['detail']}" @@ -254,32 +318,71 @@ def _model(self): @classmethod def _validate_mutation(cls, user, **data): - if type(user) is AnonymousUser or not user.id or not user.has_perms(ContractConfig.gql_mutation_update_contract_perms): + if ( + type(user) is AnonymousUser + or not user.id + or not user.has_perms(ContractConfig.gql_mutation_update_contract_perms) + ): raise ValidationError("mutation.authentication_required") @classmethod def _mutate(cls, user, **data): if "client_mutation_id" in data: - data.pop('client_mutation_id') + data.pop("client_mutation_id") if "client_mutation_label" in data: - data.pop('client_mutation_label') + data.pop("client_mutation_label") output = cls.create_cd_from_ph_insuree(user=user, data=data) - return None if output["success"] else f"Error! - {output['message']}: {output['detail']}" + return ( + None + if output["success"] + else f"Error! - {output['message']}: {output['detail']}" + ) @classmethod def create_cd_from_ph_insuree(cls, user, data): contract_details_service = ContractDetailsService(user=user) - data_contract = { - "id": data["contract_id"] - } - data_insuree = { - "id": data["policy_holder_insuree_id"] - } - output_data = contract_details_service.ph_insuree_to_contract_details( - contract=data_contract, - ph_insuree=data_insuree - ) - return output_data + try: + if not user.has_perms( + ContractConfig.gql_mutation_update_contract_perms + ): + raise PermissionError("Unauthorized") + contract = Contract.get(id=f'{data["contract_id"]}') + if contract.state not in [ + Contract.STATE_DRAFT, + Contract.STATE_REQUEST_FOR_INFORMATION, + Contract.STATE_COUNTER, + ]: + raise Exception( + _( + "You cannot update contract by adding insuree - contract not in updatable state!" + ) + ) + contract_details = contract_details_service.get_details_from_ph_insuree( + contract, + ph_insuree_id=data["policy_holder_insuree_id"] + ) + dict_representation = [] + for cd in contract_details: + uuid_string = f"{cd.id}" + record = model_to_dict(cd) + record["id"], record["uuid"] = ( + uuid_string, + uuid_string, + ) + dict_representation.append(record) + return _output_result_success(dict_representation) + else: + raise Exception( + _( + "You cannot insuree - is deleted or not enough data to create contract!" + ) + ) + except Exception as exc: + return _output_exception( + model_name="ContractDetails", + method="PHInsureToCDetatils", + exception=exc, + ) class ContractCreateInvoiceMutationMixin: @@ -290,17 +393,19 @@ def _model(self): @classmethod def _validate_mutation(cls, user, **data): - if type(user) is AnonymousUser or not user.id or not user.has_perms( - ContractConfig.gql_invoice_create_perms): + if ( + type(user) is AnonymousUser + or not user.id + or not user.has_perms(ContractConfig.gql_invoice_create_perms) + ): raise ValidationError("mutation.authentication_required") @classmethod def _mutate(cls, user, **data): - client_mutation_id = data.get("client_mutation_id") if "client_mutation_id" in data: - data.pop('client_mutation_id') + data.pop("client_mutation_id") if "client_mutation_label" in data: - data.pop('client_mutation_label') + data.pop("client_mutation_label") output = cls.create_contract_invoice(user=user, data=data) if output["success"]: return None @@ -309,9 +414,11 @@ def _mutate(cls, user, **data): @classmethod def create_contract_invoice(cls, user, data): - queryset = Contract.objects.filter(id=data['id']) + queryset = Contract.objects.filter(id=data["id"]) if queryset.count() == 1: contract = queryset.first() contract_to_invoice_service = ContractToInvoiceService(user=user) - output_data = contract_to_invoice_service.create_invoice(instance=contract, convert_to='InvoiceLine', user=user) + output_data = contract_to_invoice_service.create_invoice( + instance=contract, convert_to="InvoiceLine", user=user + ) return output_data diff --git a/contract/gql/gql_types.py b/contract/gql/gql_types.py index 9989e74..44b4310 100644 --- a/contract/gql/gql_types.py +++ b/contract/gql/gql_types.py @@ -1,13 +1,22 @@ import graphene -from core import prefix_filterset, ExtendedConnection +from contribution.gql_queries import PremiumGQLType +from contribution_plan.gql.gql_types import ( + ContributionPlanBundleGQLType, + ContributionPlanGQLType, +) +from core import ExtendedConnection, prefix_filterset from graphene_django import DjangoObjectType -from contract.models import Contract, ContractDetails, ContractContributionPlanDetails, \ - ContractMutation, ContractDetailsMutation from insuree.schema import InsureeGQLType -from contribution_plan.gql.gql_types import ContributionPlanGQLType, ContributionPlanBundleGQLType -from contribution.gql_queries import PremiumGQLType from policyholder.gql.gql_types import PolicyHolderGQLType +from contract.models import ( + Contract, + ContractContributionPlanDetails, + ContractDetails, + ContractDetailsMutation, + ContractMutation, +) + class ContractGQLType(DjangoObjectType): @@ -17,7 +26,9 @@ class Meta: filter_fields = { "id": ["exact"], "code": ["exact", "istartswith", "icontains", "iexact"], - **prefix_filterset("policy_holder__", PolicyHolderGQLType._meta.filter_fields), + **prefix_filterset( + "policy_holder__", PolicyHolderGQLType._meta.filter_fields + ), "amount_notified": ["exact", "lt", "lte", "gt", "gte"], "amount_rectified": ["exact", "lt", "lte", "gt", "gte"], "amount_due": ["exact", "lt", "lte", "gt", "gte"], @@ -49,7 +60,10 @@ class Meta: "id": ["exact"], **prefix_filterset("contract__", ContractGQLType._meta.filter_fields), **prefix_filterset("insuree__", InsureeGQLType._meta.filter_fields), - **prefix_filterset("contribution_plan_bundle__", ContributionPlanBundleGQLType._meta.filter_fields), + **prefix_filterset( + "contribution_plan_bundle__", + ContributionPlanBundleGQLType._meta.filter_fields, + ), "date_created": ["exact", "lt", "lte", "gt", "gte"], "date_updated": ["exact", "lt", "lte", "gt", "gte"], "is_deleted": ["exact"], @@ -70,8 +84,12 @@ class Meta: interfaces = (graphene.relay.Node,) filter_fields = { "id": ["exact"], - **prefix_filterset("contract_details__", ContractDetailsGQLType._meta.filter_fields), - **prefix_filterset("contribution_plan__", ContributionPlanGQLType._meta.filter_fields), + **prefix_filterset( + "contract_details__", ContractDetailsGQLType._meta.filter_fields + ), + **prefix_filterset( + "contribution_plan__", ContributionPlanGQLType._meta.filter_fields + ), **prefix_filterset("contribution__", PremiumGQLType._meta.filter_fields), "date_created": ["exact", "lt", "lte", "gt", "gte"], "date_updated": ["exact", "lt", "lte", "gt", "gte"], @@ -93,4 +111,4 @@ class Meta: class ContractDetailsMutationGQLType(DjangoObjectType): class Meta: - model = ContractDetailsMutation \ No newline at end of file + model = ContractDetailsMutation diff --git a/contract/migrations/0023_contractcontributionplandetails_amount_and_more.py b/contract/migrations/0023_contractcontributionplandetails_amount_and_more.py new file mode 100644 index 0000000..affc50d --- /dev/null +++ b/contract/migrations/0023_contractcontributionplandetails_amount_and_more.py @@ -0,0 +1,48 @@ +# Generated by Django 4.2.16 on 2024-10-30 08:31 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("policy", "0010_merge_20240912_1248"), + ("contract", "0022_add_admin_rights"), + ] + + operations = [ + migrations.AddField( + model_name="contractcontributionplandetails", + name="amount", + field=models.DecimalField( + blank=True, + db_column="Amount", + decimal_places=2, + max_digits=18, + null=True, + ), + ), + migrations.AddField( + model_name="historicalcontractcontributionplandetails", + name="amount", + field=models.DecimalField( + blank=True, + db_column="Amount", + decimal_places=2, + max_digits=18, + null=True, + ), + ), + migrations.AlterField( + model_name="contractcontributionplandetails", + name="policy", + field=models.ForeignKey( + blank=True, + db_column="PolicyID", + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + to="policy.policy", + ), + ), + ] diff --git a/contract/models.py b/contract/models.py index e37daca..2c3d587 100644 --- a/contract/models.py +++ b/contract/models.py @@ -1,14 +1,13 @@ -import uuid - -from contribution_plan.models import ContributionPlanBundle, ContributionPlan +from contribution.models import Premium +from contribution_plan.models import ContributionPlan, ContributionPlanBundle +from core import fields +from core import models as core_models from django.conf import settings from django.db import models -from core import models as core_models, fields from graphql import ResolveInfo +from insuree.models import Insuree from policy.models import Policy -from contribution.models import Premium from policyholder.models import PolicyHolder -from insuree.models import Insuree class ContractManager(models.Manager): @@ -21,17 +20,34 @@ def filter(self, *args, **kwargs): class Contract(core_models.HistoryBusinessModel): - code = models.CharField(db_column='Code', max_length=64, null=False) - policy_holder = models.ForeignKey(PolicyHolder, db_column="PolicyHolderUUID", - on_delete=models.deletion.DO_NOTHING, blank=True, null=True) - amount_notified = models.FloatField(db_column='AmountNotified', blank=True, null=True) - amount_rectified = models.FloatField(db_column='AmountRectified', blank=True, null=True) - amount_due = models.FloatField(db_column='AmountDue', blank=True, null=True) - date_approved = fields.DateTimeField(db_column='DateApproved', blank=True, null=True) - date_payment_due = fields.DateField(db_column='DatePaymentDue', blank=True, null=True) - state = models.SmallIntegerField(db_column='State', blank=True, null=True) - payment_reference = models.CharField(db_column='PaymentReference', max_length=255, blank=True, null=True) - amendment = models.IntegerField(db_column='Amendment', blank=False, null=False, default=0) + code = models.CharField(db_column="Code", max_length=64, null=False) + policy_holder = models.ForeignKey( + PolicyHolder, + db_column="PolicyHolderUUID", + on_delete=models.deletion.DO_NOTHING, + blank=True, + null=True, + ) + amount_notified = models.FloatField( + db_column="AmountNotified", blank=True, null=True + ) + amount_rectified = models.FloatField( + db_column="AmountRectified", blank=True, null=True + ) + amount_due = models.FloatField(db_column="AmountDue", blank=True, null=True) + date_approved = fields.DateTimeField( + db_column="DateApproved", blank=True, null=True + ) + date_payment_due = fields.DateField( + db_column="DatePaymentDue", blank=True, null=True + ) + state = models.SmallIntegerField(db_column="State", blank=True, null=True) + payment_reference = models.CharField( + db_column="PaymentReference", max_length=255, blank=True, null=True + ) + amendment = models.IntegerField( + db_column="Amendment", blank=False, null=False, default=0 + ) objects = ContractManager() @@ -60,7 +76,7 @@ def get_queryset(cls, queryset, user): return queryset class Meta: - db_table = 'tblContract' + db_table = "tblContract" STATE_REQUEST_FOR_INFORMATION = 1 STATE_DRAFT = 2 @@ -85,15 +101,19 @@ def filter(self, *args, **kwargs): class ContractDetails(core_models.HistoryModel): - contract = models.ForeignKey(Contract, db_column="ContractUUID", - on_delete=models.deletion.CASCADE) - insuree = models.ForeignKey(Insuree, db_column='InsureeID', - on_delete=models.deletion.DO_NOTHING) - contribution_plan_bundle = models.ForeignKey(ContributionPlanBundle, - db_column='ContributionPlanBundleUUID', - on_delete=models.deletion.DO_NOTHING) - - json_param = models.JSONField(db_column='Json_param', blank=True, null=True) + contract = models.ForeignKey( + Contract, db_column="ContractUUID", on_delete=models.deletion.CASCADE + ) + insuree = models.ForeignKey( + Insuree, db_column="InsureeID", on_delete=models.deletion.DO_NOTHING + ) + contribution_plan_bundle = models.ForeignKey( + ContributionPlanBundle, + db_column="ContributionPlanBundleUUID", + on_delete=models.deletion.DO_NOTHING, + ) + + json_param = models.JSONField(db_column="Json_param", blank=True, null=True) objects = ContractDetailsManager() @@ -109,7 +129,7 @@ def get_queryset(cls, queryset, user): return queryset class Meta: - db_table = 'tblContractDetails' + db_table = "tblContractDetails" class ContractContributionPlanDetailsManager(models.Manager): @@ -118,19 +138,40 @@ def filter(self, *args, **kwargs): for key in keys: new_key = key.replace("itemsvc", self.model.model_prefix) kwargs[new_key] = kwargs.pop(key) - return super(ContractContributionPlanDetailsManager, self).filter(*args, **kwargs) + return super(ContractContributionPlanDetailsManager, self).filter( + *args, **kwargs + ) class ContractContributionPlanDetails(core_models.HistoryBusinessModel): - contribution_plan = models.ForeignKey(ContributionPlan, db_column='ContributionPlanUUID', - on_delete=models.deletion.DO_NOTHING) - policy = models.ForeignKey(Policy, db_column='PolicyID', - on_delete=models.deletion.DO_NOTHING) - contract_details = models.ForeignKey(ContractDetails, db_column='ContractDetailsUUID', - on_delete=models.deletion.CASCADE) - contribution = models.ForeignKey(Premium, db_column='ContributionId', - related_name='contract_contribution_plan_details', on_delete=models.deletion.DO_NOTHING, - blank=True, null=True) + contribution_plan = models.ForeignKey( + ContributionPlan, + db_column="ContributionPlanUUID", + on_delete=models.deletion.DO_NOTHING, + ) + policy = models.ForeignKey( + Policy, + db_column="PolicyID", + on_delete=models.deletion.DO_NOTHING, + blank=True, + null=True, + ) + contract_details = models.ForeignKey( + ContractDetails, + db_column="ContractDetailsUUID", + on_delete=models.deletion.CASCADE, + ) + contribution = models.ForeignKey( + Premium, + db_column="ContributionId", + related_name="contract_contribution_plan_details", + on_delete=models.deletion.DO_NOTHING, + blank=True, + null=True, + ) + amount = models.DecimalField( + db_column='Amount', + max_digits=18, decimal_places=2, blank=True, null=True) objects = ContractContributionPlanDetailsManager() @@ -146,12 +187,14 @@ def get_queryset(cls, queryset, user): return queryset class Meta: - db_table = 'tblContractContributionPlanDetails' + db_table = "tblContractContributionPlanDetails" class ContractMutation(core_models.UUIDModel, core_models.ObjectMutation): - contract = models.ForeignKey(Contract, models.DO_NOTHING, related_name='mutations') - mutation = models.ForeignKey(core_models.MutationLog, models.DO_NOTHING, related_name='contracts') + contract = models.ForeignKey(Contract, models.DO_NOTHING, related_name="mutations") + mutation = models.ForeignKey( + core_models.MutationLog, models.DO_NOTHING, related_name="contracts" + ) class Meta: managed = True @@ -159,9 +202,13 @@ class Meta: class ContractDetailsMutation(core_models.UUIDModel, core_models.ObjectMutation): - contract_detail = models.ForeignKey(ContractDetails, models.DO_NOTHING, related_name='mutations') - mutation = models.ForeignKey(core_models.MutationLog, models.DO_NOTHING, related_name='contract_details') + contract_detail = models.ForeignKey( + ContractDetails, models.DO_NOTHING, related_name="mutations" + ) + mutation = models.ForeignKey( + core_models.MutationLog, models.DO_NOTHING, related_name="contract_details" + ) class Meta: managed = True - db_table = "contract_contractDetailsMutation" \ No newline at end of file + db_table = "contract_contractDetailsMutation" diff --git a/contract/schema.py b/contract/schema.py index 0965f7f..8ef193d 100644 --- a/contract/schema.py +++ b/contract/schema.py @@ -1,26 +1,48 @@ import graphene import graphene_django_optimizer as gql_optimizer - -from django.db.models import Q -from .services import check_unique_code from core.gql_queries import ValidationMessageGQLType -from core.schema import signal_mutation_module_before_mutating, OrderedDjangoFilterConnectionField +from core.schema import ( + OrderedDjangoFilterConnectionField, + signal_mutation_module_before_mutating, +) from core.utils import append_validity_filter -from contract.models import Contract, ContractDetails, \ - ContractContributionPlanDetails, ContractMutation -from contract.gql.gql_types import ContractGQLType, ContractDetailsGQLType, \ - ContractContributionPlanDetailsGQLType - -from contract.gql.gql_mutations.contract_mutations import CreateContractMutation, \ - UpdateContractMutation, DeleteContractMutation, SubmitContractMutation, ApproveContractMutation, \ - ApproveContractBulkMutation, CounterContractMutation, \ - AmendContractMutation, RenewContractMutation, CounterContractBulkMutation, ContractCreateInvoiceBulkMutation -from contract.gql.gql_mutations.contract_details_mutations import CreateContractDetailsMutation, \ - UpdateContractDetailsMutation, DeleteContractDetailsMutation, \ - CreateContractDetailByPolicyHolderInsureeMutation +from django.db.models import Q + from contract.apps import ContractConfig +from contract.gql.gql_mutations.contract_details_mutations import ( + CreateContractDetailByPolicyHolderInsureeMutation, + CreateContractDetailsMutation, + DeleteContractDetailsMutation, + UpdateContractDetailsMutation, +) +from contract.gql.gql_mutations.contract_mutations import ( + AmendContractMutation, + ApproveContractBulkMutation, + ApproveContractMutation, + ContractCreateInvoiceBulkMutation, + CounterContractBulkMutation, + CounterContractMutation, + CreateContractMutation, + DeleteContractMutation, + RenewContractMutation, + SubmitContractMutation, + UpdateContractMutation, +) +from contract.gql.gql_types import ( + ContractContributionPlanDetailsGQLType, + ContractDetailsGQLType, + ContractGQLType, +) +from contract.models import ( + Contract, + ContractContributionPlanDetails, + ContractDetails, + ContractMutation, +) from contract.utils import filter_amount_contract +from .services import check_unique_code + class Query(graphene.ObjectType): @@ -33,7 +55,7 @@ class Query(graphene.ObjectType): dateValidTo__Lte=graphene.DateTime(), amount_from=graphene.Decimal(), amount_to=graphene.Decimal(), - applyDefaultValidityFilter=graphene.Boolean() + applyDefaultValidityFilter=graphene.Boolean(), ) contract_details = OrderedDjangoFilterConnectionField( @@ -52,14 +74,16 @@ class Query(graphene.ObjectType): validate_contract_code = graphene.Field( ValidationMessageGQLType, contract_code=graphene.String(required=True), - description="Check that the specified contract code is unique." + description="Check that the specified contract code is unique.", ) def resolve_validate_contract_code(self, info, **kwargs): if not info.context.user.has_perms(ContractConfig.gql_query_contract_perms): - if not info.context.user.has_perms(ContractConfig.gql_query_contract_policyholder_portal_perms): + if not info.context.user.has_perms( + ContractConfig.gql_query_contract_policyholder_portal_perms + ): raise PermissionError("Unauthorized") - errors = check_unique_code(code=kwargs['contract_code']) + errors = check_unique_code(code=kwargs["contract_code"]) if errors: return ValidationMessageGQLType(False) else: @@ -67,51 +91,59 @@ def resolve_validate_contract_code(self, info, **kwargs): def resolve_contract(self, info, **kwargs): if not info.context.user.has_perms(ContractConfig.gql_query_contract_perms): - if not info.context.user.has_perms(ContractConfig.gql_query_contract_policyholder_portal_perms): + if not info.context.user.has_perms( + ContractConfig.gql_query_contract_policyholder_portal_perms + ): raise PermissionError("Unauthorized") filters = append_validity_filter(**kwargs) client_mutation_id = kwargs.get("client_mutation_id", None) if client_mutation_id: - filters.append(Q(mutations__mutation__client_mutation_id=client_mutation_id)) + filters.append( + Q(mutations__mutation__client_mutation_id=client_mutation_id) + ) - insuree = kwargs.get('insuree', None) + insuree = kwargs.get("insuree", None) if insuree: filters.append(Q(contractdetails__insuree__uuid=insuree)) # amount filters - amount_from = kwargs.get('amount_from', None) - amount_to = kwargs.get('amount_to', None) + amount_from = kwargs.get("amount_from", None) + amount_to = kwargs.get("amount_to", None) if amount_from or amount_to: filters.append(filter_amount_contract(**kwargs)) return gql_optimizer.query(Contract.objects.filter(*filters).all(), info) def resolve_contract_details(self, info, **kwargs): if not info.context.user.has_perms(ContractConfig.gql_query_contract_perms): - if not info.context.user.has_perms(ContractConfig.gql_query_contract_policyholder_portal_perms): + if not info.context.user.has_perms( + ContractConfig.gql_query_contract_policyholder_portal_perms + ): raise PermissionError("Unauthorized") filters = [] client_mutation_id = kwargs.get("client_mutation_id", None) if client_mutation_id: - filters.append(Q(mutations__mutation__client_mutation_id=client_mutation_id)) + filters.append( + Q(mutations__mutation__client_mutation_id=client_mutation_id) + ) return gql_optimizer.query(ContractDetails.objects.filter(*filters).all(), info) def resolve_contract_contribution_plan_details(self, info, **kwargs): if not info.context.user.has_perms(ContractConfig.gql_query_contract_perms): - if not info.context.user.has_perms(ContractConfig.gql_query_contract_policyholder_portal_perms): + if not info.context.user.has_perms( + ContractConfig.gql_query_contract_policyholder_portal_perms + ): raise PermissionError("Unauthorized") query = ContractContributionPlanDetails.objects.all() - insuree = kwargs.get('insuree', None) - contribution_plan_bundle = kwargs.get('contributionPlanBundle', None) + insuree = kwargs.get("insuree", None) + contribution_plan_bundle = kwargs.get("contributionPlanBundle", None) if insuree: - query = query.filter( - contract_details__insuree__uuid=insuree - ) + query = query.filter(contract_details__insuree__uuid=insuree) if contribution_plan_bundle: query = query.filter( @@ -137,19 +169,23 @@ class Mutation(graphene.ObjectType): create_contract_details = CreateContractDetailsMutation.Field() update_contract_details = UpdateContractDetailsMutation.Field() delete_contract_details = DeleteContractDetailsMutation.Field() - create_contract_details_by_ph_insuree = CreateContractDetailByPolicyHolderInsureeMutation.Field() + create_contract_details_by_ph_insuree = ( + CreateContractDetailByPolicyHolderInsureeMutation.Field() + ) def on_contract_mutation(sender, **kwargs): - uuids = kwargs['data'].get('uuids', []) + uuids = kwargs["data"].get("uuids", []) if not uuids: - uuid = kwargs['data'].get('uuid', None) + uuid = kwargs["data"].get("uuid", None) uuids = [uuid] if uuid else [] if not uuids: return [] impacted_contracts = Contract.objects.filter(id__in=uuids).all() for contract in impacted_contracts: - ContractMutation.objects.update_or_create(contract=contract, mutation_id=kwargs['mutation_log_id']) + ContractMutation.objects.update_or_create( + contract=contract, mutation_id=kwargs["mutation_log_id"] + ) return [] diff --git a/contract/services.py b/contract/services.py index c39462e..650ab45 100644 --- a/contract/services.py +++ b/contract/services.py @@ -1,42 +1,36 @@ import json -import uuid +import logging import traceback -from django.conf import settings +import uuid from copy import copy -from django.utils.translation import gettext as _ -from django.core.exceptions import ValidationError -from .config import get_message_counter_contract - -from django.db.models.query import Q +from calculation.services import run_calculation_rules +from contribution.models import Premium +from core import datetime, datetimedelta +from core.signals import register_service_signal +from dateutil.relativedelta import relativedelta +from django.conf import settings from django.contrib.auth.models import AnonymousUser +from django.core.exceptions import ValidationError +from django.core.mail import BadHeaderError, send_mail from django.core.serializers.json import DjangoJSONEncoder -from django.core.mail import send_mail, BadHeaderError +from django.db.models.query import Q from django.forms.models import model_to_dict - -from contract.apps import ContractConfig -from contract.signals import signal_contract, signal_contract_approve -from contract.models import Contract as ContractModel, \ - ContractDetails as ContractDetailsModel, \ - ContractContributionPlanDetails as ContractContributionPlanDetailsModel -from calculation.services import run_calculation_rules - -from policyholder.models import PolicyHolderInsuree, PolicyHolder -from contribution.models import Premium -from contribution_plan.models import ContributionPlanBundleDetails, ContributionPlan - -from policy.models import Policy +from django.utils.translation import gettext as _ from payment.models import Payment, PaymentDetail from payment.services import update_or_create_payment -from insuree.models import Insuree - -from dateutil.relativedelta import relativedelta +from policy.models import Policy +from policyholder.models import PolicyHolderInsuree -from core.signals import register_service_signal -from core import datetimedelta, datetime -from core.utils import filter_validity +from contract.apps import ContractConfig +from contract.models import Contract as ContractModel +from contract.models import ( + ContractContributionPlanDetails as ContractContributionPlanDetailsModel, +) +from contract.models import ContractDetails as ContractDetailsModel +from contract.signals import signal_contract, signal_contract_approve -import logging +from .config import get_message_counter_contract logger = logging.getLogger(__file__) @@ -73,14 +67,17 @@ def __init__(self, user): @check_authentication def create(self, contract): try: - incoming_code = contract.get('code') + incoming_code = contract.get("code") if check_unique_code(incoming_code): - raise ValidationError(_("Contract code %s already exists" % incoming_code)) - if not self.user.has_perms(ContractConfig.gql_mutation_create_contract_perms): + raise ValidationError( + _("Contract code %s already exists" % incoming_code) + ) + if not self.user.has_perms( + ContractConfig.gql_mutation_create_contract_perms + ): raise PermissionError(_("Unauthorized")) - if ( - not contract.get('date_valid_to', None) or - not contract.get('date_valid_from', None) + if not contract.get("date_valid_to", None) or not contract.get( + "date_valid_from", None ): raise Exception(_("contract.mandatory_fields.date_valid")) c = ContractModel(**contract) @@ -93,70 +90,98 @@ def create(self, contract): if c.policy_holder: # run services updateFromPHInsuree and Contract Valuation cd_service = ContractDetails(user=self.user) - result_ph_insuree = cd_service.update_from_ph_insuree(contract=c) - if result_ph_insuree['success']: + result_ph_insuree = cd_service.get_details_from_ph_insuree(contract=c) + if result_ph_insuree["success"]: result_contract_valuation = self.contract_valuation( - contract_details_list=result_ph_insuree["data"] + contract=c, contract_details_list=result_ph_insuree["data"] ) else: return result_ph_insuree - if not result_contract_valuation or result_contract_valuation["success"] is False: - logger.error(_("contract valuation failed %s" % str(result_contract_valuation))) - raise Exception(_("contract valuation failed %s" % str(result_contract_valuation))) - c.amount_notified = result_contract_valuation['data']['total_amount'] + if ( + not result_contract_valuation + or result_contract_valuation["success"] is False + ): + logger.error( + _( + "contract valuation failed %s" + % str(result_contract_valuation) + ) + ) + raise Exception( + _( + "contract valuation failed %s" + % str(result_contract_valuation) + ) + ) + c.amount_notified = result_contract_valuation["data"]["total_amount"] historical_record = c.history.all().last() c.json_ext = _save_json_external( user_id=str(historical_record.user_updated.id), datetime=str(historical_record.date_updated), - message=_("create contract status %s" % historical_record.state) + message=_("create contract status %s" % historical_record.state), ) c.save(username=self.user.username) dict_representation = model_to_dict(c) - dict_representation['id'], dict_representation['uuid'] = (str(uuid_string), str(uuid_string)) + dict_representation["id"], dict_representation["uuid"] = ( + str(uuid_string), + str(uuid_string), + ) except Exception as exc: - return _output_exception(model_name="Contract", method="create", exception=exc) + return _output_exception( + model_name="Contract", method="create", exception=exc + ) return _output_result_success(dict_representation=dict_representation) - # TODO update contract scenario according to wiki page @check_authentication def update(self, contract): try: # check rights for contract / amendments - if not (self.user.has_perms(ContractConfig.gql_mutation_update_contract_perms) or self.user.has_perms( - ContractConfig.gql_mutation_approve_ask_for_change_contract_perms)): + if not ( + self.user.has_perms(ContractConfig.gql_mutation_update_contract_perms) + or self.user.has_perms( + ContractConfig.gql_mutation_approve_ask_for_change_contract_perms + ) + ): raise PermissionError("Unauthorized") - updated_contract = ContractModel.objects.filter(id=contract['id']).first() + updated_contract = ContractModel.objects.filter(id=contract["id"]).first() # updatable scenario if self.__check_rights_by_status(updated_contract.state) == "updatable": if "code" in contract: - raise ContractUpdateError(_("That fields are not editable in that permission!")) + raise ContractUpdateError( + _("That fields are not editable in that permission!") + ) return _output_result_success( dict_representation=self.__update_contract_fields( - contract_input=contract, - updated_contract=updated_contract + contract_input=contract, updated_contract=updated_contract ) ) # approvable scenario if self.__check_rights_by_status(updated_contract.state) == "approvable": # in “Negotiable” changes are possible only with the authority “Approve/ask for change” - if not self.user.has_perms(ContractConfig.gql_mutation_approve_ask_for_change_contract_perms): + if not self.user.has_perms( + ContractConfig.gql_mutation_approve_ask_for_change_contract_perms + ): raise PermissionError(_("unauthorized")) return _output_result_success( dict_representation=self.__update_contract_fields( - contract_input=contract, - updated_contract=updated_contract + contract_input=contract, updated_contract=updated_contract ) ) if self.__check_rights_by_status(updated_contract.state) == "cannot_update": raise ContractUpdateError(_("contract.validation.locked_by_state")) except Exception as exc: - return _output_exception(model_name="Contract", method="update", exception=exc) + return _output_exception( + model_name="Contract", method="update", exception=exc + ) def __check_rights_by_status(self, status): state = "cannot_update" - if status in [ContractModel.STATE_DRAFT, ContractModel.STATE_REQUEST_FOR_INFORMATION, - ContractModel.STATE_COUNTER]: + if status in [ + ContractModel.STATE_DRAFT, + ContractModel.STATE_REQUEST_FOR_INFORMATION, + ContractModel.STATE_COUNTER, + ]: state = "updatable" if status == ContractModel.STATE_NEGOTIABLE: state = "approvable" @@ -168,72 +193,86 @@ def __update_contract_fields(self, contract_input, updated_contract): [setattr(updated_contract, key, contract_input[key]) for key in contract_input] # check if PH is set and not changed if current_policy_holder_id: - if "policy_holder" in updated_contract.get_dirty_fields(check_relationship=True): - raise ContractUpdateError(_("You cannot update already set PolicyHolder in Contract!")) + if "policy_holder" in updated_contract.get_dirty_fields( + check_relationship=True + ): + raise ContractUpdateError( + _("You cannot update already set PolicyHolder in Contract!") + ) updated_contract.save(username=self.user.username) # save the communication historical_record = updated_contract.history.all().first() updated_contract.json_ext = _save_json_external( user_id=str(historical_record.user_updated.id), datetime=str(historical_record.date_updated), - message=_("update contract status %s" % str(historical_record.state)) + message=_("update contract status %s" % str(historical_record.state)), ) updated_contract.save(username=self.user.username) uuid_string = f"{updated_contract.id}" dict_representation = model_to_dict(updated_contract) - dict_representation["id"], dict_representation["uuid"] = (str(uuid_string), str(uuid_string)) + dict_representation["id"], dict_representation["uuid"] = ( + str(uuid_string), + str(uuid_string), + ) return dict_representation @check_authentication def submit(self, contract): try: # check for submittion right perms/authorites - if not self.user.has_perms(ContractConfig.gql_mutation_submit_contract_perms): + if not self.user.has_perms( + ContractConfig.gql_mutation_submit_contract_perms + ): raise PermissionError("Unauthorized") contract_id = f"{contract['id']}" contract_to_submit = ContractModel.objects.filter(id=contract_id).first() if not contract_to_submit: - raise ContractUpdateError(_("No contract found for this id %s" % contract['id'])) - errors, contract_details_list = self.__gather_policy_holder_insuree( - self.__validate_submission(contract_to_submit=contract_to_submit), - contract_to_submit.amendment, - contract_date_valid_from=None, - ) - if errors: - raise Exception(_("errors during insuree loading: %s" % ", ".join(errors))) + raise ContractUpdateError( + _("No contract found for this id %s" % contract["id"]) + ) + contract_to_submit = self.__validate_submission(contract_to_submit=contract_to_submit) + # contract valuation - valuation_result = self.contract_valuation( - contract_details_list, - ) - if valuation_result['success']: - contract_to_submit.amount_rectified = valuation_result['data']["total_amount"] + valuation_result = self.contract_valuation(contract_to_submit) + if valuation_result["success"]: + contract_to_submit.amount_rectified = valuation_result["data"][ + "total_amount" + ] else: - raise Exception(_("Unable to valuate contract: %s" % valuation_result['message'])) + raise Exception( + _("Unable to valuate contract: %s" % valuation_result["message"]) + ) # send signal contract_to_submit.state = ContractModel.STATE_NEGOTIABLE - signal_contract.send(sender=ContractModel, contract=contract_to_submit, user=self.user) + signal_contract.send( + sender=ContractModel, contract=contract_to_submit, user=self.user + ) dict_representation = model_to_dict(contract_to_submit) - dict_representation["id"], dict_representation["uuid"] = (contract_id, contract_id) + dict_representation["id"], dict_representation["uuid"] = ( + contract_id, + contract_id, + ) return _output_result_success(dict_representation=dict_representation) except Exception as exc: - return _output_exception(model_name="Contract", method="submit", exception=exc) + return _output_exception( + model_name="Contract", method="submit", exception=exc + ) @staticmethod def contract_business_validity(contract): return [ - Q(date_valid_to__isnull=True) | Q(date_valid_to__gte=contract.date_valid_from), - Q(date_valid_from__lte=contract.date_valid_to) + Q(date_valid_to__isnull=True) + | Q(date_valid_to__gte=contract.date_valid_from), + Q(date_valid_from__lte=contract.date_valid_to), ] - def __validate_submission(self, contract_to_submit): # check if we have a PolicyHoldes and any ContractDetails if not contract_to_submit.policy_holder: raise ContractUpdateError(_("The contract does not contain PolicyHolder!")) contract_details = ContractDetailsModel.objects.filter( - contract=contract_to_submit, - is_deleted=False + contract=contract_to_submit, is_deleted=False ) if contract_details.count() == 0: raise ContractUpdateError(_("contract.validation.no_details")) @@ -241,100 +280,71 @@ def __validate_submission(self, contract_to_submit): state_right = self.__check_rights_by_status(contract_to_submit.state) # check if we can submit if state_right == "cannot_update": - raise ContractUpdateError(_("The contract cannot be submitted because of current state!")) + raise ContractUpdateError( + _("The contract cannot be submitted because of current state!") + ) if state_right == "approvable": raise ContractUpdateError(_("The contract has been already submitted!")) return contract_to_submit - def __gather_policy_holder_insuree(self, contract, amendment=None, contract_date_valid_from=None): - result = [] - if not amendment: - amendment = contract.amendment - contract_details = ContractDetailsModel.objects.filter(contract=contract, is_deleted=False) - errors = [] - policy_id = None - for cd in contract_details: - error = None - if isinstance(contract.policy_holder, PolicyHolder): - ph_insuree = PolicyHolderInsuree.objects.filter( - *self.contract_business_validity(contract), - insuree_id=cd.insuree_id, - is_deleted=False, - - ).order_by('-date_valid_from').first() - if ph_insuree: - policy_id = ph_insuree.last_policy_id - else: - error = _("insuree %s not affiliated with policyholder" % cd.insuree_id) - if not error and not policy_id: - policy = Policy.objects.filter( - insuree_policies__insuree_id=cd.insuree_id, - *filter_validity(), - *filter_validity(prefix='insuree_policies__') - ).order_by('-expiry_date').values('id').first() - if policy: - policy_id = policy['id'] - if not error: - result.append({ - "id": f"{cd.id}", - "contribution_plan_bundle": f"{cd.contribution_plan_bundle_id}", - "policy_id": policy_id, - "json_ext": cd.json_ext if cd.json_ext else '', - "contract_date_valid_from": contract.date_valid_from, - "insuree_id": cd.insuree_id, - "amendment": amendment - }) - else: - errors.append(error) - return errors, result - @check_authentication def approve(self, contract): try: # check for approve/ask for change right perms/authorites - if not self.user.has_perms(ContractConfig.gql_mutation_approve_ask_for_change_contract_perms): + if not self.user.has_perms( + ContractConfig.gql_mutation_approve_ask_for_change_contract_perms + ): raise PermissionError(_("unauthorized")) contract_id = f"{contract['id']}" - contract_to_approve = ContractModel.objects.filter( - id=contract_id, - is_deleted=False - ).order_by('-amendment').first() + contract_to_approve = ( + ContractModel.objects.filter(id=contract_id, is_deleted=False) + .order_by("-amendment") + .first() + ) if not contract_to_approve: - raise ContractUpdateError(_("No contract found for this id %s" % contract['id'])) + raise ContractUpdateError( + _("No contract found for this id %s" % contract["id"]) + ) # variable to check if we have right to approve state_right = self.__check_rights_by_status(contract_to_approve.state) # check if we can submit if state_right != "approvable": - raise ContractUpdateError(_("You cannot approve this contract! The status of contract is not Negotiable!")) - contract_details_list = {} - errors, contract_details_list["data"] = self.__gather_policy_holder_insuree(contract_to_approve) - if errors: - raise Exception(_("errors during insuree loading: %s" % ", ".join(errors))) + raise ContractUpdateError( + _( + "You cannot approve this contract! The status of contract is not Negotiable!" + ) + ) # send signal - approve contract ccpd_service = ContractContributionPlanDetails(user=self.user) payment_service = PaymentService(user=self.user) signal_contract_approve.send( sender=ContractModel, - contract=contract_to_approve, + instance=contract_to_approve, user=self.user, - contract_details_list=contract_details_list, service_object=self, payment_service=payment_service, - ccpd_service=ccpd_service + ccpd_service=ccpd_service, ) # ccpd.create_contribution(contract_contribution_plan_details) dict_representation = {} id_contract_approved = f"{contract_to_approve.id}" - dict_representation["id"], dict_representation["uuid"] = id_contract_approved, id_contract_approved + dict_representation["id"], dict_representation["uuid"] = ( + id_contract_approved, + id_contract_approved, + ) return _output_result_success(dict_representation=dict_representation) except Exception as exc: - return _output_exception(model_name="Contract", method="approve", exception=exc) + return _output_exception( + model_name="Contract", method="approve", exception=exc + ) @check_authentication def counter(self, contract): try: # check for approve/ask for change right perms/authorites - if not self.user.has_perms(ContractConfig.gql_mutation_approve_ask_for_change_contract_perms): + if not self.user.has_perms( + ContractConfig.gql_mutation_approve_ask_for_change_contract_perms + ): raise PermissionError("Unauthorized") contract_id = f"{contract['id']}" contract_to_counter = ContractModel.objects.filter(id=contract_id).first() @@ -342,11 +352,20 @@ def counter(self, contract): state_right = self.__check_rights_by_status(contract_to_counter.state) # check if we can submit if state_right != "approvable": - raise ContractUpdateError(_("You cannot counter this contract! The status of contract is not Negotiable!")) + raise ContractUpdateError( + _( + "You cannot counter this contract! The status of contract is not Negotiable!" + ) + ) contract_to_counter.state = ContractModel.STATE_COUNTER - signal_contract.send(sender=ContractModel, contract=contract_to_counter, user=self.user) + signal_contract.send( + sender=ContractModel, contract=contract_to_counter, user=self.user + ) dict_representation = model_to_dict(contract_to_counter) - dict_representation["id"], dict_representation["uuid"] = (contract_id, contract_id) + dict_representation["id"], dict_representation["uuid"] = ( + contract_id, + contract_id, + ) _send_email_notify_counter( code=contract_to_counter.code, name=contract_to_counter.policy_holder.trade_name, @@ -355,20 +374,27 @@ def counter(self, contract): ) return _output_result_success(dict_representation=dict_representation) except Exception as exc: - return _output_exception(model_name="Contract", method="counter", exception=exc) + return _output_exception( + model_name="Contract", method="counter", exception=exc + ) @check_authentication def amend(self, contract): try: # check for amend right perms/authorites - if not self.user.has_perms(ContractConfig.gql_mutation_amend_contract_perms): + if not self.user.has_perms( + ContractConfig.gql_mutation_amend_contract_perms + ): raise PermissionError("Unauthorized") contract_id = f"{contract['id']}" contract_to_amend = ContractModel.objects.filter(id=contract_id).first() # variable to check if we have right to amend contract state_right = self.__check_rights_by_status(contract_to_amend.state) # check if we can amend - if state_right != "cannot_update" and contract_to_amend.state != ContractModel.STATE_TERMINATED: + if ( + state_right != "cannot_update" + and contract_to_amend.state != ContractModel.STATE_TERMINATED + ): raise ContractUpdateError(_("You cannot amend this contract!")) # create copy of the contract amended_contract = copy(contract_to_amend) @@ -377,39 +403,55 @@ def amend(self, contract): amended_contract.state = ContractModel.STATE_DRAFT contract_to_amend.state = ContractModel.STATE_ADDENDUM from core import datetime + contract_to_amend.date_valid_to = datetime.datetime.now() # update contract - also copy contract details etc contract.pop("id") [setattr(amended_contract, key, contract[key]) for key in contract] # check if chosen fields are not edited - if any(dirty_field in ["policy_holder", "code", "date_valid_from"] for dirty_field in - amended_contract.get_dirty_fields(check_relationship=True)): - raise ContractUpdateError(_("You cannot update this field during amend contract!")) - signal_contract.send(sender=ContractModel, contract=contract_to_amend, user=self.user) - signal_contract.send(sender=ContractModel, contract=amended_contract, user=self.user) + if any( + dirty_field in ["policy_holder", "code", "date_valid_from"] + for dirty_field in amended_contract.get_dirty_fields( + check_relationship=True + ) + ): + raise ContractUpdateError( + _("You cannot update this field during amend contract!") + ) + signal_contract.send( + sender=ContractModel, contract=contract_to_amend, user=self.user + ) + signal_contract.send( + sender=ContractModel, contract=amended_contract, user=self.user + ) # copy also contract details - self.__copy_details(contract_id=contract_id, modified_contract=amended_contract) - # evaluate amended contract amount notified - - errors, contract_details_list = self.__gather_policy_holder_insuree( - amended_contract, - contract_to_amend.amendment + self.__copy_details( + contract_id=contract_id, modified_contract=amended_contract ) - if errors: - raise Exception(_("errors during insuree loading: %s" % ", ".join(errors))) - valuation_result = self.contract_valuation(contract_details_list) - amended_contract.amount_notified = valuation_result['data']["total_amount"] + # evaluate amended contract amount notified + + valuation_result = self.contract_valuation(amended_contract) + amended_contract.amount_notified = valuation_result["data"]["total_amount"] if "amount_notified" in amended_contract.get_dirty_fields(): - signal_contract.send(sender=ContractModel, contract=amended_contract, user=self.user) + signal_contract.send( + sender=ContractModel, contract=amended_contract, user=self.user + ) amended_contract_dict = model_to_dict(amended_contract) id_new_amended = f"{amended_contract.id}" - amended_contract_dict["id"], amended_contract_dict["uuid"] = (id_new_amended, id_new_amended) + amended_contract_dict["id"], amended_contract_dict["uuid"] = ( + id_new_amended, + id_new_amended, + ) return _output_result_success(dict_representation=amended_contract_dict) except Exception as exc: - return _output_exception(model_name="Contract", method="amend", exception=exc) + return _output_exception( + model_name="Contract", method="amend", exception=exc + ) def __copy_details(self, contract_id, modified_contract): - list_cd = list(ContractDetailsModel.objects.filter(contract_id=contract_id).all()) + list_cd = list( + ContractDetailsModel.objects.filter(contract_id=contract_id).all() + ) for cd in list_cd: cd_new = copy(cd) cd_new.id = None @@ -420,54 +462,83 @@ def __copy_details(self, contract_id, modified_contract): def renew(self, contract): try: # check rights for renew contract - if not self.user.has_perms(ContractConfig.gql_mutation_renew_contract_perms): + if not self.user.has_perms( + ContractConfig.gql_mutation_renew_contract_perms + ): raise PermissionError("Unauthorized") contract_to_renew = ContractModel.objects.filter(id=contract["id"]).first() contract_id = contract["id"] # block renewing contract not in Updateable or Approvable state state_right = self.__check_rights_by_status(contract_to_renew.state) # check if we can renew - if state_right != "cannot_update" and contract_to_renew.state != ContractModel.STATE_TERMINATED: + if ( + state_right != "cannot_update" + and contract_to_renew.state != ContractModel.STATE_TERMINATED + ): raise ContractUpdateError(_("You cannot renew this contract!")) # create copy of the contract - later we also copy contract detail renewed_contract = copy(contract_to_renew) # TO DO : if a policyholder is set, the contract details must be removed and PHinsuree imported again renewed_contract.id = None # Date to (the previous contract) became date From of the new contract (TBC if we need to add 1 day) - # Date To of the new contract is calculated by DateFrom new contract + “Duration in month of previous contract“ - length_contract = (contract_to_renew.date_valid_to.year - contract_to_renew.date_valid_from.year) * 12 \ - + (contract_to_renew.date_valid_to.month - contract_to_renew.date_valid_from.month) - renewed_contract.date_valid_from = contract_to_renew.date_valid_to + datetimedelta(days=1) - renewed_contract.date_valid_to = contract_to_renew.date_valid_to + datetimedelta(months=length_contract) - renewed_contract.state, renewed_contract.version = (ContractModel.STATE_DRAFT, 1) + # Date To of the new contract is calculated by DateFrom new contract + # + “Duration in month of previous contract“ + length_contract = ( + contract_to_renew.date_valid_to.year + - contract_to_renew.date_valid_from.year + ) * 12 + ( + contract_to_renew.date_valid_to.month + - contract_to_renew.date_valid_from.month + ) + renewed_contract.date_valid_from = ( + contract_to_renew.date_valid_to + datetimedelta(days=1) + ) + renewed_contract.date_valid_to = ( + contract_to_renew.date_valid_to + datetimedelta(months=length_contract) + ) + renewed_contract.state, renewed_contract.version = ( + ContractModel.STATE_DRAFT, + 1, + ) renewed_contract.amount_rectified, renewed_contract.amount_due = (0, 0) renewed_contract.save(username=self.user.username) historical_record = renewed_contract.history.all().first() renewed_contract.json_ext = _save_json_external( user_id=str(historical_record.user_updated.id), datetime=str(historical_record.date_updated), - message=f"contract renewed - state " - f"{historical_record.state}" + message=f"contract renewed - state " f"{historical_record.state}", ) renewed_contract.save(username=self.user.username) # copy also contract details - self.__copy_details(contract_id=contract_id, modified_contract=renewed_contract) + self.__copy_details( + contract_id=contract_id, modified_contract=renewed_contract + ) renewed_contract_dict = model_to_dict(renewed_contract) id_new_renewed = f"{renewed_contract.id}" - renewed_contract_dict["id"], renewed_contract_dict["uuid"] = (id_new_renewed, id_new_renewed) + renewed_contract_dict["id"], renewed_contract_dict["uuid"] = ( + id_new_renewed, + id_new_renewed, + ) return _output_result_success(dict_representation=renewed_contract_dict) except Exception as exc: - return _output_exception(model_name="Contract", method="renew", exception=exc) + return _output_exception( + model_name="Contract", method="renew", exception=exc + ) @check_authentication def delete(self, contract): try: # check rights for delete contract - if not self.user.has_perms(ContractConfig.gql_mutation_delete_contract_perms): + if not self.user.has_perms( + ContractConfig.gql_mutation_delete_contract_perms + ): raise PermissionError(_("unauthorized")) contract_to_delete = ContractModel.objects.filter(id=contract["id"]).first() # block deleting contract not in Updateable or Approvable state - if self.__check_rights_by_status(contract_to_delete.state) == "cannot_update": + if ( + self.__check_rights_by_status(contract_to_delete.state) + == "cannot_update" + ): raise ContractUpdateError(_("Contract in that state cannot be deleted")) contract_to_delete.delete(username=self.user.username) return { @@ -476,7 +547,9 @@ def delete(self, contract): "detail": "", } except Exception as exc: - return _output_exception(model_name="Contract", method="delete", exception=exc) + return _output_exception( + model_name="Contract", method="delete", exception=exc + ) @check_authentication def terminate_contract(self): @@ -484,9 +557,12 @@ def terminate_contract(self): # TODO - add this service to the tasks.py in apscheduler once a day # to check if contract might be terminated from core import datetime - contracts_to_terminate = list(ContractModel.objects.filter( - Q(date_valid_to__lt=datetime.datetime.now(), state=7) - )) + + contracts_to_terminate = list( + ContractModel.objects.filter( + Q(date_valid_to__lt=datetime.datetime.now(), state=ContractModel.STATE_EFFECTIVE) + ) + ) if len(contracts_to_terminate) > 0: for contract in contracts_to_terminate: # we can marked that contract as a terminated @@ -497,7 +573,7 @@ def terminate_contract(self): user_id=str(historical_record.user_updated.id), datetime=str(historical_record.date_updated), message=f"contract terminated - state " - f"{historical_record.state}" + f"{historical_record.state}", ) contract.save(username=self.user.username) return { @@ -512,7 +588,9 @@ def terminate_contract(self): "detail": _("We do not have any contract to be terminated!"), } except Exception as exc: - return _output_exception(model_name="Contract", method="terminateContract", exception=exc) + return _output_exception( + model_name="Contract", method="terminateContract", exception=exc + ) @check_authentication def get_negative_amount_amendment(self, credit_note): @@ -521,13 +599,16 @@ def get_negative_amount_amendment(self, credit_note): raise PermissionError("Unauthorized") contract_output_list = [] - payment_detail = PaymentDetail.get_queryset(PaymentDetail.objects.filter( - payment__id=credit_note["id"] - )).prefetch_related( - 'premium__contract_contribution_plan_details__contract_details__contract' - ).prefetch_related( - 'premium__contract_contribution_plan_details' - ).filter(premium__contract_contribution_plan_details__isnull=False) + payment_detail = ( + PaymentDetail.get_queryset( + PaymentDetail.objects.filter(payment__id=credit_note["id"]) + ) + .prefetch_related( + "premium__contract_contribution_plan_details__contract_details__contract" + ) + .prefetch_related("premium__contract_contribution_plan_details") + .filter(premium__contract_contribution_plan_details__isnull=False) + ) if len(list(payment_detail)) > 0: contribution_list_id = [pd.premium.id for pd in payment_detail] @@ -536,84 +617,116 @@ def get_negative_amount_amendment(self, credit_note): ).distinct() for contract in contract_list: # look for approved contract (amendement) - if contract.state in [ContractModel.STATE_EFFECTIVE, - ContractModel.STATE_EXECUTABLE] and contract.amendment > 0: + if ( + contract.state + in [ + ContractModel.STATE_EFFECTIVE, + ContractModel.STATE_EXECUTABLE, + ] + and contract.amendment > 0 + ): # get the contract which has the negative amount due if contract.amount_due < 0: contract_dict = model_to_dict(contract) contract_id = f"{contract.id}" - contract_dict["id"], contract_dict["uuid"] = (contract_id, contract_id) + contract_dict["id"], contract_dict["uuid"] = ( + contract_id, + contract_id, + ) contract_output_list.append(contract_dict) # TODO not only get that contracts - but also do another things (it must be specified on wiki page) return _output_result_success(dict_representation=contract_output_list) except Exception as exc: - return _output_exception(model_name="Contract", method="getNegativeAmountAmendment", exception=exc) - - - def contract_valuation(self, contract_details_list, save=False): + return _output_exception( + model_name="Contract", + method="getNegativeAmountAmendment", + exception=exc, + ) + + def contract_valuation(self, contract, contract_details_list=None, save=False): ccpd_service = ContractContributionPlanDetails(self.user) try: + if contract_details_list is None: + contract_details_list = contract.contractdetails_set.filter(is_deleted=False) dict_representation = {} total_amount = 0 - amendment = 0 + amendment = contract.amendment ccpd_record = [] + errors = [] for contract_details in contract_details_list: - cpbd_list = ContributionPlanBundleDetails.objects.filter( - contribution_plan_bundle__id=str(contract_details["contribution_plan_bundle"]) - ) - amendment = contract_details["amendment"] + cpbd_list = contract_details.contribution_plan_bundle.contributionplanbundledetails_set.all() + for cpbd in cpbd_list: ccpd = ContractContributionPlanDetailsModel( **{ - "contract_details_id": contract_details["id"], - "contribution_plan_id": f"{cpbd.contribution_plan.id}", - "policy_id": contract_details["policy_id"], + "contract_details": contract_details, + "contribution_plan": cpbd.contribution_plan, + "date_valid_from": contract.date_valid_from, + "date_valid_to": contract.date_valid_to, } - ) + ) + ccpd.amount = run_calculation_rules(ccpd, "value", self.user) + + if ccpd.amount in (False, None): + errors.append( + f"no amount calculated for {ccpd.contract_details.insuree}" + + f" - {ccpd.contribution_plan.code}" + ) # value from strategy - calculated_amount = 0 - calculated_amount = run_calculation_rules(ccpd, "value", self.user) or 0 - total_amount += calculated_amount - + + total_amount += ccpd.amount + if save: - ccpd_record.extend(ccpd_service.split(ccpd, contract_details["insuree_id"], calculated_amount)) + ccpd_record.extend( + ccpd_service.split( + ccpd, contract_details.insuree + ) + ) else: - record = model_to_dict(ccpd) - record['calculated_amount'] = calculated_amount - ccpd_record.append(record) - - + ccpd_record.append(ccpd) + if errors: + raise Exception("failed to compute values:" + ',\n'.join(errors)) if amendment > 0: # get the payment from the previous version of the contract - contract_detail_id = contract_details_list[0]["id"] + contract_detail_id = contract_details_list[0].id cd = ContractDetailsModel.objects.get(id=contract_detail_id) contract_previous = ContractModel.objects.filter( Q(amendment=amendment - 1, code=cd.contract.code) ).first() - premium = ContractContributionPlanDetailsModel.objects.filter( - contract_details__contract__id=f'{contract_previous.id}' - ).first().contribution - payment_detail_contribution = PaymentDetail.objects.filter(premium=premium).first() + premium = ( + ContractContributionPlanDetailsModel.objects.filter( + contract_details__contract__id=f"{contract_previous.id}" + ) + .first() + .contribution + ) + payment_detail_contribution = PaymentDetail.objects.filter( + premium=premium + ).first() payment_id = payment_detail_contribution.payment.id payment_object = Payment.objects.get(id=payment_id) - total_amount -= payment_object.received_amount if payment_object.received_amount else 0 - dict_representation['total_amount'] = total_amount - dict_representation['contribution_plan_details'] = ccpd_record - return _output_result_success(dict_representation=dict_representation) + total_amount -= ( + payment_object.received_amount + if payment_object.received_amount + else 0 + ) + dict_representation["total_amount"] = total_amount + dict_representation["contribution_plan_details"] = ccpd_record + return _output_result_success(dict_representation, object_list=True) except Exception as exc: return _output_exception( model_name="ContractContributionPlanDetails", method="contractValuation", - exception=exc + exception=exc, ) class ContractDetails(object): def __init__(self, user): self.user = user -#contract_details - @check_authentication - def update_from_ph_insuree(self, contract): + + # contract_details + def get_details_from_ph_insuree(self, contract, ph_insuree=None): contract_insuree_list = [] if not contract.policy_holder: _output_result_success(dict_representation=contract_insuree_list) @@ -621,8 +734,13 @@ def update_from_ph_insuree(self, contract): policy_holder_insuree = PolicyHolderInsuree.objects.filter( *Contract.contract_business_validity(contract), policy_holder=contract.policy_holder, - is_deleted=False + is_deleted=False, ) + if ph_insuree and isinstance(ph_insuree, list): + policy_holder_insuree = policy_holder_insuree.filter( + id__in=ph_insuree + ) + for phi in policy_holder_insuree: # TODO add the validity condition also! if phi.contribution_plan_bundle: @@ -630,58 +748,19 @@ def update_from_ph_insuree(self, contract): **{ "contract_id": contract.id, "insuree_id": phi.insuree.id, - "contribution_plan_bundle_id": f"{phi.contribution_plan_bundle.id}", + "contribution_plan_bundle": phi.contribution_plan_bundle, "json_ext": phi.json_ext, } ) - # TODO add only the caclulation_rule section - cd.save(username=self.user.username) - uuid_string = f"{cd.id}" - dict_representation = model_to_dict(cd) - dict_representation["id"], dict_representation["uuid"] = (uuid_string, uuid_string) - dict_representation["policy_id"] = phi.last_policy.id if phi.last_policy else None - dict_representation["amendment"] = contract.amendment - dict_representation["contract_date_valid_from"] = cd.contract.date_valid_from - contract_insuree_list.append(dict_representation) - except Exception as exc: - return _output_exception(model_name="ContractDetails", method="updateFromPHInsuree", exception=exc) - return _output_result_success(dict_representation=contract_insuree_list) - - @check_authentication - def ph_insuree_to_contract_details(self, contract, ph_insuree): - try: - phi = PolicyHolderInsuree.objects.get(id=f'{ph_insuree["id"]}') - # check for update contract right perms/authorites - if not self.user.has_perms(ContractConfig.gql_mutation_update_contract_perms): - raise PermissionError("Unauthorized") - if phi.is_deleted is False and phi.contribution_plan_bundle: - updated_contract = ContractModel.objects.get(id=f'{contract["id"]}') - if updated_contract.state not in [ContractModel.STATE_DRAFT, - ContractModel.STATE_REQUEST_FOR_INFORMATION, - ContractModel.STATE_COUNTER]: - raise ContractUpdateError( - _("You cannot update contract by adding insuree - contract not in updatable state!") - ) - if updated_contract.policy_holder is None: - raise ContractUpdateError( - _("There is no policy holder in contract!") - ) - cd = ContractDetailsModel( - **{ - "contract_id": contract["id"], - "insuree_id": phi.insuree.id, - "contribution_plan_bundle_id": f"{phi.contribution_plan_bundle.id}", - } - ) - cd.save(username=self.user.username) - uuid_string = f"{cd.id}" - dict_representation = model_to_dict(cd) - dict_representation["id"], dict_representation["uuid"] = (uuid_string, uuid_string) - return _output_result_success(dict_representation=dict_representation) - else: - raise ContractUpdateError(_("You cannot insuree - is deleted or not enough data to create contract!")) + cd.save(user=self.user) + contract_insuree_list.append(cd) except Exception as exc: - return _output_exception(model_name="ContractDetails", method="PHInsureToCDetatils", exception=exc) + return _output_exception( + model_name="ContractDetails", + method="updateFromPHInsuree", + exception=exc, + ) + return _output_result_success(contract_insuree_list, object_list=True) class ContractContributionPlanDetails(object): @@ -690,103 +769,120 @@ def __init__(self, user): self.user = user @check_authentication - def split(self, ccpd, insuree_id, calculated_amount): - """" - method to create contract contribution plan details + def split(self, ccpd, insuree): + """ " + method to create contract contribution plan details """ - date_valid_to = ccpd.date_valid_from + datetimedelta(months=ccpd.contribution_plan.periodicity) + date_valid_from = ccpd.date_valid_from # get date from strategy validity_dates = run_calculation_rules( ccpd, "validity", self.user, - validity_from=date_valid_from, - validity_to=date_valid_to + validity_from=ccpd.contract_details.contract.date_valid_from, + validity_to=ccpd.contract_details.contract.date_valid_to, ) - - if validity_dates and 'effective_date' in validity_dates and validity_dates['effective_date']: - date_valid_from = datetime.date.from_ad_date(validity_dates['effective_date']) - - if validity_dates and 'expiry_date' in validity_dates and validity_dates['expiry_date']: - date_valid_to = datetime.date.from_ad_date(validity_dates['expiry_date']) + + if ( + validity_dates + and "effective_date" in validity_dates + and validity_dates["effective_date"] + ): + date_valid_from = datetime.date.from_ad_date( + validity_dates["effective_date"] + ) + + if ( + validity_dates + and "expiry_date" in validity_dates + and validity_dates["expiry_date"] + ): + date_valid_to = datetime.date.from_ad_date(validity_dates["expiry_date"]) # get the relevant policy from the related product of contribution plan # policy objects get all related to this product - insuree = Insuree.objects.filter(id=insuree_id).first() policies = self.__get_policy( insuree=insuree, date_valid_from=date_valid_from, date_valid_to=date_valid_to, - product=ccpd.contribution_plan.benefit_plan + product=ccpd.contribution_plan.benefit_plan, ) + calculated_amount = ccpd.amount list_ccpd = [] amount_booked = 0 - unit_amount = (calculated_amount / (date_valid_to - date_valid_from).days) - len_policies = len(policies) - if len_policies >= 1: - ccpd.policy = policies[0] - + unit_amount = calculated_amount / (date_valid_to - date_valid_from).days + i = 0 + for policy in policies: + if i > 1: + ccpd = ccpd.copy() + i += 1 + ccpd.policy = policy ccpd.date_valid_from = max( - datetime.datetime.fromordinal(policies[0].effective_date.toordinal()), - date_valid_from) + datetime.datetime.fromordinal(policy.effective_date.toordinal()), + date_valid_from, + ) ccpd.date_valid_to = min( - datetime.datetime.fromordinal(policies[0].expiry_date.toordinal()), - date_valid_to) + datetime.datetime.fromordinal(policy.expiry_date.toordinal()), + date_valid_to, + ) + + if len(policies) == i: + ccpd.amount = calculated_amount - amount_booked + else: + ccpd.amount = round( + (ccpd.date_valid_to - ccpd.date_valid_from).days * unit_amount + ) + amount_booked += ccpd.amount ccpd.save(user=self.user) - record = model_to_dict(ccpd) - record['id'] = ccpd.id - amount = round((ccpd.date_valid_to - ccpd.date_valid_from).days * unit_amount) - amount_booked += amount - record['calculated_amount'] = amount - list_ccpd.append(record) - - if len_policies > 1: - i = 1 - for policy in policies[1:]: - # create second ccpd because another policy was created - copy object and save - i += 1 - ccpd_new = copy(ccpd) - ccpd_new.date_valid_from = max(policy.effective_date, date_valid_from) - ccpd_new.date_valid_to = min(policy.expiry_date, date_valid_to) - ccpd_new.policy = policy - ccpd_new.save(user=self.user.user) - record = model_to_dict(ccpd_new) - record['id'] = ccpd_new.id - record['calculated_amount'] = amount - if ccpd_new.date_valid_to == date_valid_to: - record['calculated_amount'] = calculated_amount - amount_booked - else: - amount = (ccpd_new.date_valid_to - ccpd_new.date_valid_from).days * unit_amount - amount_booked += amount - record['calculated_amount'] = amount - list_ccpd.append(record) - + amount_booked += ccpd.amount + list_ccpd.append(ccpd) return list_ccpd def __get_policy(self, insuree, date_valid_from, date_valid_to, product): - policy_output = [] + # get all policies related to the product and insuree - policies = Policy.objects.filter( - product=product, - family__head_insuree=insuree, - expiry_date__gte=date_valid_from - ).order_by('start_date').values('start_date', 'expiry_date') - date_ranges = list(map(lambda p: (p['start_date'], p['expiry_date']), policies)) - missing_coverage = subtract_date_ranges((date_valid_from, date_valid_to,), date_ranges) - - for data in missing_coverage: - policy_created, last_date_covered = self.create_contract_details_policies(insuree, product, data[0], - data[1]) + policies = ( + Policy.objects.filter( + product=product, + insuree_policies__insuree=insuree, + expiry_date__gte=date_valid_from, + ) + .order_by("start_date") + .distinct() + ) + policy_output = list(policies) + date_ranges = [(p.start_date, p.expiry_date,) for p in policies] + + missing_coverage = subtract_date_ranges( + ( + date_valid_from, + date_valid_to, + ), + date_ranges, + ) + if missing_coverage: + policy_created, last_date_covered = self.create_contract_details_policies( + insuree, product, missing_coverage + ) if policy_created is not None and len(policy_created) > 0: policy_output += policy_created return policy_output - def create_contract_details_policies(self, insuree, product, last_date_covered, date_valid_to): + def create_contract_details_policies( + self, insuree, product, missing_coverage + ): # create policy for insuree familly # TODO Policy with status - new open=32 in policy-be_py module policy_output = [] + sorted_list = sorted(missing_coverage, key=lambda x: x[0]) + + last_date_covered = sorted_list[0][0] + date_valid_to = sorted_list[-1][1] + while last_date_covered < date_valid_to: - expiry_date = last_date_covered + relativedelta(months=product.insurance_period) + expiry_date = last_date_covered + relativedelta( + months=product.insurance_period + ) cur_policy = Policy.objects.create( **{ "family": insuree.family, @@ -806,44 +902,46 @@ def create_contract_details_policies(self, insuree, product, last_date_covered, policy_output.append(cur_policy) return policy_output, last_date_covered - - - @check_authentication def create_contribution(self, contract_contribution_plan_details): try: dict_representation = {} contribution_list = [] from core import datetime + now = datetime.datetime.now() for ccpd in contract_contribution_plan_details["contribution_plan_details"]: - contract_details = ContractDetailsModel.objects.get(id=f"{ccpd['contract_details']}") + # contract_details = ContractDetailsModel.objects.get( + # id=f"{ccpd['contract_details']}" + # ) # create the contributions based on the ContractContributionPlanDetails - if ccpd["contribution"] is None and ccpd["calculated_amount"] > 0 : + if ccpd.contribution is None: contribution = Premium.objects.create( **{ - "policy_id": ccpd["policy"], - "amount": ccpd["calculated_amount"], - "audit_user_id": -1, + "policy": ccpd.policy, + "amount": ccpd.amount, + "audit_user_id": self.user._u.audit_user_id, "pay_date": now, # TODO Temporary value pay_type - I have to get to know about this field what should be here # also ask about audit_user_id and pay_date value "pay_type": " ", - "receipt": str(uuid.uuid4()) + "receipt": str(uuid.uuid4()), } ) - ccpd_object = ContractContributionPlanDetailsModel.objects.get(id=ccpd["id"]) - ccpd_object.contribution = contribution - ccpd_object.save(username=self.user.username) + + ccpd.contribution = contribution + ccpd.save(user=self.user) contribution_record = model_to_dict(contribution) contribution_list.append(contribution_record) dict_representation["contributions"] = contribution_list + else: + pass return _output_result_success(dict_representation=dict_representation) except Exception as exc: return _output_exception( model_name="ContractContributionPlanDetails", method="createContribution", - exception=exc + exception=exc, ) @@ -858,10 +956,11 @@ def create(self, payment, payment_details=None): dict_representation = {} payment_list = [] from core import datetime + now = datetime.datetime.now() p = update_or_create_payment(data=payment, user=self.user) dict_representation = model_to_dict(p) - dict_representation['id'], dict_representation['uuid'] = (p.id, p.uuid) + dict_representation["id"], dict_representation["uuid"] = (p.id, p.uuid) if payment_details: for payment_detail in payment_details: pd = PaymentDetail.objects.create( @@ -871,33 +970,33 @@ def create(self, payment, payment_details=None): product_code=payment_detail["product_code"], insurance_number=payment_detail["insurance_number"], expected_amount=payment_detail["expected_amount"], - premium=payment_detail["premium"] + premium=payment_detail["premium"], ) pd_record = model_to_dict(pd) - pd_record['id'] = pd.id + pd_record["id"] = pd.id payment_list.append(pd_record) dict_representation["payment_details"] = payment_list return _output_result_success(dict_representation=dict_representation) except Exception as exc: return _output_exception( - model_name="Payment", - method="createPayment", - exception=exc + model_name="Payment", method="createPayment", exception=exc ) def collect_payment_details(self, contract_contribution_plan_details): payment_details_data = [] for ccpd in contract_contribution_plan_details: - product_code = ContributionPlan.objects.get(id=ccpd["contribution_plan"]).benefit_plan.code - insurance_number = ContractDetailsModel.objects.get(id=ccpd["contract_details"]).insuree.chf_id - contribution = ContractContributionPlanDetailsModel.objects.get(id=ccpd["id"]).contribution - expected_amount = ccpd["calculated_amount"] - payment_details_data.append({ - "product_code": product_code, - "insurance_number": insurance_number, - "expected_amount": expected_amount, - "premium": contribution - }) + product_code = ccpd.contribution_plan.benefit_plan.code + insurance_number = ccpd.contract_details.insuree.chf_id + contribution = ccpd.contribution + expected_amount = ccpd.amount + payment_details_data.append( + { + "product_code": product_code, + "insurance_number": insurance_number, + "expected_amount": expected_amount, + "premium": contribution, + } + ) return payment_details_data @@ -907,9 +1006,9 @@ def __init__(self, user): self.user = user @classmethod - @register_service_signal('create_invoice_from_contract') + @register_service_signal("create_invoice_from_contract") def create_invoice(self, instance, convert_to="Invoice", **kwargs): - """ run convert the ContractContributionPlanDetails of the contract to invoice lines""" + """run convert the ContractContributionPlanDetails of the contract to invoice lines""" pass @@ -923,32 +1022,33 @@ def _output_exception(model_name, method, exception): } -def _output_result_success(dict_representation): +def _output_result_success(dict_representation, object_list=False): return { "success": True, "message": "Ok", "detail": "", - "data": json.loads(json.dumps(dict_representation, cls=DjangoJSONEncoder)), + "data": ( + json.loads(json.dumps(dict_representation, cls=DjangoJSONEncoder)) + if not object_list + else dict_representation + ), } def _save_json_external(user_id, datetime, message): return { - "comments": [{ - "From": "Portal/webapp", - "user": user_id, - "date": datetime, - "msg": message - }] + "comments": [ + {"From": "Portal/webapp", "user": user_id, "date": datetime, "msg": message} + ] } def _send_email_notify_counter(code, name, contact_name, email): try: email_to_send = send_mail( - subject='Contract counter notification', + subject="Contract counter notification", message=get_message_counter_contract( - language=settings.LANGUAGE_CODE.split('-')[0], + language=settings.LANGUAGE_CODE.split("-")[0], code=code, name=name, contact_name=contact_name, @@ -959,7 +1059,7 @@ def _send_email_notify_counter(code, name, contact_name, email): ) return email_to_send except BadHeaderError: - return ValueError('Invalid header found.') + return ValueError("Invalid header found.") def check_unique_code(code): @@ -968,7 +1068,6 @@ def check_unique_code(code): return [] - def subtract_date_ranges(main_range, date_ranges): main_start, main_end = main_range result = [] @@ -981,7 +1080,7 @@ def subtract_date_ranges(main_range, date_ranges): # If there's a gap before the current range, add it to the result if current < start: result.append((current, min(start, main_end))) - + # Move the current pointer current = max(current, end) diff --git a/contract/signals.py b/contract/signals.py index 1c26b63..4d3603f 100644 --- a/contract/signals.py +++ b/contract/signals.py @@ -1,12 +1,12 @@ -from .config import get_message_approved_contract -from .models import Contract, ContractContributionPlanDetails +from calculation.services import run_calculation_rules from core.signals import Signal -from django.db.models import Q -from django.db.models.signals import post_save from django.conf import settings -from django.core.mail import send_mail, BadHeaderError +from django.core.mail import BadHeaderError, send_mail +from django.db.models import Q, Subquery +from django.db.models.signals import post_save from django.dispatch import receiver from insuree.apps import InsureeConfig +from insuree.models import InsureePolicy from insuree.signals import signal_before_insuree_policy_query from payment.apps import PaymentConfig from payment.models import Payment, PaymentDetail @@ -14,12 +14,19 @@ from policy.signals import signal_check_formal_sector_for_policy from policyholder.apps import PolicyholderConfig from policyholder.models import PolicyHolderUser -from insuree.models import InsureePolicy -from calculation.services import run_calculation_rules + +from .config import get_message_approved_contract +from .models import Contract, ContractContributionPlanDetails _contract_signal_params = ["contract", "user"] -_contract_approve_signal_params = ["contract", "user", "contract_details_list", "service_object", "payment_service", - "ccpd_service"] +_contract_approve_signal_params = [ + "contract", + "user", + "contract_details_list", + "service_object", + "payment_service", + "ccpd_service", +] signal_contract = Signal(_contract_signal_params) signal_contract_approve = Signal(_contract_signal_params) @@ -31,42 +38,58 @@ def on_contract_signal(sender, **kwargs): return f"contract updated - state {contract.state}" -def on_contract_approve_signal(sender, **kwargs): +def on_contract_approve_signal(sender, instance, **kwargs): # approve scenario user = kwargs["user"] - contract_to_approve = kwargs["contract"] - contract_details_list = kwargs["contract_details_list"] contract_service = kwargs["service_object"] payment_service = kwargs["payment_service"] ccpd_service = kwargs["ccpd_service"] # contract valuation contract_contribution_plan_details = contract_service.contract_valuation( - contract_details_list['data'], - save=True + instance, save=True ) - if not contract_contribution_plan_details['success']: + if not contract_contribution_plan_details["success"]: return contract_contribution_plan_details - contract_to_approve.amount_due = contract_contribution_plan_details['data']["total_amount"] - result = ccpd_service.create_contribution(contract_contribution_plan_details['data']) - result_payment = __create_payment(contract_to_approve, payment_service, contract_contribution_plan_details['data']) + instance.amount_due = contract_contribution_plan_details["data"][ + "total_amount" + ] + contribution_result = ccpd_service.create_contribution( + contract_contribution_plan_details["data"] + ) + if not contribution_result.get('success', False): + return contribution_result + result_payment = __create_payment( + instance, payment_service, contract_contribution_plan_details["data"] + ) + if not result_payment.get('success', False): + return result_payment # STATE_EXECUTABLE from core import datetime + now = datetime.datetime.now() - contract_to_approve.date_approved = now - contract_to_approve.state = 5 - approved_contract = __save_or_update_contract(contract=contract_to_approve, user=user) - if contract_to_approve.policy_holder.email: - email_contact_name = contract_to_approve.policy_holder.contact_name["contactName"] \ - if (contract_to_approve.policy_holder.contact_name and "contactName" in contract_to_approve.policy_holder.contact_name) \ - else contract_to_approve.policy_holder.trade_name or contract_to_approve.policy_holder.code - email = __send_email_notify_payment( - code=contract_to_approve.code, - name=contract_to_approve.policy_holder.trade_name, + instance.date_approved = now + instance.state = 5 + approved_contract = __save_or_update_contract( + contract=instance, user=user + ) + if instance.policy_holder.email: + email_contact_name = ( + instance.policy_holder.contact_name["contactName"] + if ( + instance.policy_holder.contact_name + and "contactName" in instance.policy_holder.contact_name + ) + else instance.policy_holder.trade_name + or instance.policy_holder.code + ) + __send_email_notify_payment( + code=instance.code, + name=instance.policy_holder.trade_name, contact_name=email_contact_name, - amount_due=contract_to_approve.amount_due, - payment_reference=contract_to_approve.payment_reference, - email=contract_to_approve.policy_holder.email, + amount_due=instance.amount_due, + payment_reference=instance.payment_reference, + email=instance.policy_holder.email, ) return approved_contract @@ -74,11 +97,12 @@ def on_contract_approve_signal(sender, **kwargs): # additional filters for payment in 'contract' tab def append_contract_filter(sender, **kwargs): user = kwargs.get("user", None) - additional_filter = kwargs.get('additional_filter', None) + additional_filter = kwargs.get("additional_filter", None) if "contract" in additional_filter: # then check perms if user.has_perms(PaymentConfig.gql_query_payments_perms) or user.has_perms( - PolicyholderConfig.gql_query_payment_portal_perms): + PolicyholderConfig.gql_query_payment_portal_perms + ): contract_id = additional_filter["contract"] contract_to_process = Contract.objects.filter(id=contract_id).first() @@ -88,30 +112,38 @@ def append_contract_filter(sender, **kwargs): type_user = f"{user}" # related to user object output (i) or (t) # check if we have interactive user from current context - if '(i)' in type_user: + if "(i)" in type_user: from core import datetime + now = datetime.datetime.now() ph_user = PolicyHolderUser.objects.filter( Q(date_valid_to__isnull=True) | Q(date_valid_to__gte=now), date_valid_from__lte=now, - policy_holder__id=contract_to_process.policy_holder.id, user__id=user.id + policy_holder__id=contract_to_process.policy_holder.id, + user__id=user.id, ).first() if ph_user or user.has_perms(PaymentConfig.gql_query_payments_perms): return Q( - payment_details__premium__contract_contribution_plan_details__contract_details__contract__id=contract_id + **{ + 'payment_details__' + + 'premium__' + + 'contract_contribution_plan_details__' + + 'contract_details__contract__id': contract_id + } ) # additional filters for InsureePolicy in contract def append_contract_policy_insuree_filter(sender, **kwargs): user = kwargs.get("user", None) - additional_filter = kwargs.get('additional_filter', None) + additional_filter = kwargs.get("additional_filter", None) if "contract" in additional_filter: # then check perms - if user.has_perms(InsureeConfig.gql_query_insuree_policy_perms) or user.has_perms( - PolicyholderConfig.gql_query_insuree_policy_portal_perms): + if user.has_perms( + InsureeConfig.gql_query_insuree_policy_perms + ) or user.has_perms(PolicyholderConfig.gql_query_insuree_policy_portal_perms): contract_id = additional_filter["contract"] contract_to_process = Contract.objects.filter(id=contract_id).first() if not contract_to_process or not contract_to_process.policy_holder: @@ -120,34 +152,39 @@ def append_contract_policy_insuree_filter(sender, **kwargs): type_user = f"{user}" # related to user object output (i) or (t) # check if we have interactive user from current context - if '(i)' in type_user: + if "(i)" in type_user: from core import datetime + now = datetime.datetime.now() ph_user = PolicyHolderUser.objects.filter( Q(date_valid_to__isnull=True) | Q(date_valid_to__gte=now), date_valid_from__lte=now, - policy_holder__id=contract_to_process.policy_holder.id, - user__id=user.id + policy_holder__id=contract_to_process.policy_holder.id, + user__id=user.id, ).first() - if ph_user or user.has_perms(InsureeConfig.gql_query_insuree_policy_perms): + if ph_user or user.has_perms( + InsureeConfig.gql_query_insuree_policy_perms + ): policies = list( ContractContributionPlanDetails.objects.filter( - contract_details__contract__id=contract_id).values_list("policy", flat=True) + contract_details__contract__id=contract_id + ).values_list("policy", flat=True) ) return Q( start_date__gte=contract_to_process.date_valid_from, start_date__lte=contract_to_process.date_valid_to, - policy__in=policies + policy__in=policies, ) - # check if policy is related to formal sector contract def formal_sector_policies(sender, **kwargs): - policy_id = kwargs.get('policy_id', None) - ccpd = ContractContributionPlanDetails.objects.filter(policy__id=policy_id, is_deleted=False).first() + policy_id = kwargs.get("policy_id", None) + ccpd = ContractContributionPlanDetails.objects.filter( + policy__id=policy_id, is_deleted=False + ).first() if ccpd: cd = ccpd.contract_details contract = cd.contract @@ -157,7 +194,9 @@ def formal_sector_policies(sender, **kwargs): signal_contract.connect(on_contract_signal, dispatch_uid="on_contract_signal") -signal_contract_approve.connect(on_contract_approve_signal, dispatch_uid="on_contract_approve_signal") +signal_contract_approve.connect( + on_contract_approve_signal, dispatch_uid="on_contract_approve_signal" +) signal_before_payment_query.connect(append_contract_filter) signal_before_insuree_policy_query.connect(append_contract_policy_insuree_filter) signal_check_formal_sector_for_policy.connect(formal_sector_policies) @@ -167,39 +206,39 @@ def formal_sector_policies(sender, **kwargs): def activate_contracted_policies(sender, instance, **kwargs): received_amount = instance.received_amount if instance.received_amount else 0 # check if payment is related to the contract - payment_detail = PaymentDetail.objects.filter( - payment__id=int(instance.id) - ).prefetch_related( - 'premium__contract_contribution_plan_details__contract_details__contract' - ).prefetch_related( - 'premium__contract_contribution_plan_details' - ).filter(premium__contract_contribution_plan_details__isnull=False) + payment_detail = ( + PaymentDetail.objects.filter(payment__id=int(instance.id)) + .prefetch_related( + "premium__contract_contribution_plan_details__contract_details__contract" + ) + .prefetch_related("premium__contract_contribution_plan_details") + .filter(premium__contract_contribution_plan_details__isnull=False) + ) if len(list(payment_detail)) > 0: if instance.expected_amount <= received_amount: - contribution_list_id = [pd.premium.id for pd in payment_detail] + contribution_list_id = list(set(pd.premium.id for pd in payment_detail)) contract_list = Contract.objects.filter( contractdetails__contractcontributionplandetails__contribution__id__in=contribution_list_id ).distinct() - ccpd_number = ContractContributionPlanDetails.objects.prefetch_related('contract_details__contract').filter( - contract_details__contract__in=list(contract_list) - ).count() + ccpd_full_list = ContractContributionPlanDetails.objects.filter( + contract_details__contract__in=Subquery(contract_list.values('id')) + ).prefetch_related('contract_details') + # 1- check if the contract have payment attached to each contributions # (nbr CCPD of all contract in step 0= Paymentdetails in Steps 0) - if ccpd_number == len(list(payment_detail)): + if len(ccpd_full_list) == len(list(payment_detail)): for contract in contract_list: if contract.state == Contract.STATE_EXECUTABLE: # get the ccpd related to the currenttly processing contract - ccpd_list = list( - ContractContributionPlanDetails.objects.prefetch_related( - 'contract_details__contract').filter( - contract_details__contract=contract - ) - ) - from core import datetime, datetimedelta + ccpd_list = [ + ccpd for ccpd in ccpd_full_list if ccpd.contract_details.contract_id == contract.id + ] # TODO support Splitted payment and check that # the payment match the value of all contributions for ccpd in ccpd_list: - members = run_calculation_rules(ccpd, "members", contract.user_updated) + members = run_calculation_rules( + ccpd, "members", contract.user_updated + ) for insuree in members: InsureePolicy.objects.create( **{ @@ -218,12 +257,9 @@ def activate_contracted_policies(sender, instance, **kwargs): def __save_json_external(user_id, datetime, message): return { - "comments": [{ - "From": "Portal/webapp", - "user": user_id, - "date": datetime, - "msg": message - }] + "comments": [ + {"From": "Portal/webapp", "user": user_id, "date": datetime, "msg": message} + ] } @@ -233,8 +269,7 @@ def __save_or_update_contract(contract, user): contract.json_ext = __save_json_external( user_id=str(historical_record.user_updated.id), datetime=str(historical_record.date_updated), - message=f"contract updated - state " - f"{historical_record.state}" + message=f"contract updated - state " f"{historical_record.state}", ) contract.save(username=user.username) return contract @@ -242,27 +277,34 @@ def __save_or_update_contract(contract, user): def __create_payment(contract, payment_service, contract_cpd): from core import datetime + now = datetime.datetime.now() # format payment data payment_data = { "expected_amount": contract.amount_due, "request_date": now, } - payment_details_data = payment_service.collect_payment_details(contract_cpd["contribution_plan_details"]) - return payment_service.create(payment=payment_data, payment_details=payment_details_data) + payment_details_data = payment_service.collect_payment_details( + contract_cpd["contribution_plan_details"] + ) + return payment_service.create( + payment=payment_data, payment_details=payment_details_data + ) -def __send_email_notify_payment(code, name, contact_name, amount_due, payment_reference, email): +def __send_email_notify_payment( + code, name, contact_name, amount_due, payment_reference, email +): try: email = send_mail( - subject='Contract payment notification', + subject="Contract payment notification", message=get_message_approved_contract( - language=settings.LANGUAGE_CODE.split('-')[0], + language=settings.LANGUAGE_CODE.split("-")[0], code=code, name=name, contact_name=contact_name, due_amount=amount_due, - payment_reference=payment_reference + payment_reference=payment_reference, ), from_email=settings.EMAIL_HOST_USER, recipient_list=[email], @@ -270,4 +312,4 @@ def __send_email_notify_payment(code, name, contact_name, amount_due, payment_re ) return email except BadHeaderError: - return ValueError('Invalid header found.') + return ValueError("Invalid header found.") diff --git a/contract/tasks.py b/contract/tasks.py index 369f350..aa6bcd1 100644 --- a/contract/tasks.py +++ b/contract/tasks.py @@ -1,9 +1,10 @@ import logging from core.models import User -from contract.models import Contract, ContractContributionPlanDetails -from contract.services import Contract as ContractService, ContractToInvoiceService +from contract.models import Contract, ContractContributionPlanDetails +from contract.services import Contract as ContractService +from contract.services import ContractToInvoiceService logger = logging.getLogger(__name__) @@ -16,6 +17,7 @@ def approve_contracts(user_id, contracts): output.append(contract_service.approve(contract={"id": contract})) return output + def counter_contracts(user_id, contracts): output = [] user = User.objects.get(id=user_id) @@ -24,6 +26,7 @@ def counter_contracts(user_id, contracts): output.append(contract_service.counter(contract={"id": contract})) return output + def create_invoice_from_contracts(user_id, contracts): output = [] user = User.objects.get(id=user_id) @@ -32,11 +35,15 @@ def create_invoice_from_contracts(user_id, contracts): contract_instance = Contract.objects.filter(id=contract) if contract_instance: contract_instance = contract_instance.first() - ccpd_list = ContractContributionPlanDetails.objects.filter(contract_details__contract=contract_instance) - output.append(contract_service.create_invoice( - instance=contract_instance, - convert_to="InvoiceLine", - user=user, - ccpd_list=ccpd_list - )) + ccpd_list = ContractContributionPlanDetails.objects.filter( + contract_details__contract=contract_instance + ) + output.append( + contract_service.create_invoice( + instance=contract_instance, + convert_to="InvoiceLine", + user=user, + ccpd_list=ccpd_list, + ) + ) return output diff --git a/contract/tests/__init__.py b/contract/tests/__init__.py index c2320d6..e69de29 100644 --- a/contract/tests/__init__.py +++ b/contract/tests/__init__.py @@ -1,4 +0,0 @@ -# from .mutation_tests import * -# from .query_tests import * -from .helpers import * -from .helpers_tests import * diff --git a/contract/tests/gql_tests/__init__.py b/contract/tests/gql_tests/__init__.py deleted file mode 100644 index b0364f7..0000000 --- a/contract/tests/gql_tests/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .query_tests import * -from .mutation_create_tests import * \ No newline at end of file diff --git a/contract/tests/helpers.py b/contract/tests/helpers.py index 9a749f4..0996925 100644 --- a/contract/tests/helpers.py +++ b/contract/tests/helpers.py @@ -1,13 +1,17 @@ import datetime -from contribution_plan.tests.helpers import create_test_contribution_plan_bundle, create_test_contribution_plan +from contribution.models import Premium +from contribution_plan.tests.helpers import ( + create_test_contribution_plan, + create_test_contribution_plan_bundle, +) from core.models import User from insuree.test_helpers import create_test_insuree from policy.test_helpers import create_test_policy -from product.test_helpers import create_test_product from policyholder.tests.helpers import create_test_policy_holder -from contribution.models import Premium -from contract.models import Contract, ContractDetails, ContractContributionPlanDetails +from product.test_helpers import create_test_product + +from contract.models import Contract, ContractContributionPlanDetails, ContractDetails def create_test_contract(policy_holder=None, custom_props={}): @@ -17,16 +21,16 @@ def create_test_contract(policy_holder=None, custom_props={}): user = __get_or_create_simple_contract_user() object_data = { - 'code': "CON", - 'policy_holder': policy_holder, - 'amount_notified': 0, - 'amount_rectified': 0, - 'amount_due': 0, - 'date_payment_due': datetime.date(2011, 10, 31), - 'state': 1, - 'payment_reference': "Payment Reference", - 'json_ext': {}, - **custom_props + "code": "CON", + "policy_holder": policy_holder, + "amount_notified": 0, + "amount_rectified": 0, + "amount_due": 0, + "date_payment_due": datetime.date(2011, 10, 31), + "state": 1, + "payment_reference": "Payment Reference", + "json_ext": {}, + **custom_props, } contract = Contract(**object_data) @@ -35,8 +39,9 @@ def create_test_contract(policy_holder=None, custom_props={}): return contract -def create_test_contract_details(contract=None, insuree=None, - contribution_plan_bundle=None, custom_props={}): +def create_test_contract_details( + contract=None, insuree=None, contribution_plan_bundle=None, custom_props={} +): if not contract: contract = create_test_contract() @@ -48,11 +53,11 @@ def create_test_contract_details(contract=None, insuree=None, user = __get_or_create_simple_contract_user() object_data = { - 'contract': contract, - 'insuree': insuree, - 'contribution_plan_bundle': contribution_plan_bundle, - 'json_param': {}, - **custom_props + "contract": contract, + "insuree": insuree, + "contribution_plan_bundle": contribution_plan_bundle, + "json_param": {}, + **custom_props, } contract_details = ContractDetails(**object_data) @@ -61,15 +66,26 @@ def create_test_contract_details(contract=None, insuree=None, return contract_details -def create_test_contract_contribution_plan_details(contribution_plan=None, policy=None, - contract_details=None, contribution=None, custom_props={}): +def create_test_contract_contribution_plan_details( + contribution_plan=None, + policy=None, + contract_details=None, + contribution=None, + custom_props={}, +): if not contribution_plan: contribution_plan = create_test_contribution_plan() if not policy: policy = create_test_policy( - product=create_test_product("TestCode", custom_props={"insurance_period": 12, }), - insuree=create_test_insuree()) + product=create_test_product( + "TestCode", + custom_props={ + "insurance_period": 12, + }, + ), + insuree=create_test_insuree(), + ) if not contract_details: contract_details = create_test_contract_details() @@ -84,18 +100,18 @@ def create_test_contract_contribution_plan_details(contribution_plan=None, polic "pay_date": "2019-01-01", "validity_from": "2019-01-01", "audit_user_id": 1, - "pay_type": 'C' + "pay_type": "C", } ) user = __get_or_create_simple_contract_user() object_data = { - 'contribution_plan': contribution_plan, - 'policy': policy, - 'contract_details': contract_details, - 'contribution': contribution, - 'json_ext': {}, - **custom_props + "contribution_plan": contribution_plan, + "policy": policy, + "contract_details": contract_details, + "contribution": contribution, + "json_ext": {}, + **custom_props, } contract_contribution_plan_details = ContractContributionPlanDetails(**object_data) @@ -105,7 +121,7 @@ def create_test_contract_contribution_plan_details(contribution_plan=None, polic def __get_or_create_simple_contract_user(): - if not User.objects.filter(username='admin').exists(): - User.objects.create_superuser(username='admin', password='S\/pe®Pąßw0rd™') - user = User.objects.filter(username='admin').first() + if not User.objects.filter(username="admin").exists(): + User.objects.create_superuser(username="admin", password="S/pe®Pąßw0rd™") + user = User.objects.filter(username="admin").first() return user diff --git a/contract/tests/helpers_tests.py b/contract/tests/helpers_tests.py deleted file mode 100644 index 0a5bd02..0000000 --- a/contract/tests/helpers_tests.py +++ /dev/null @@ -1,99 +0,0 @@ -from functools import lru_cache -from django.test import TestCase - -from .helpers import * -from ..models import ContractContributionPlanDetails - - -class HelpersTest(TestCase): - """ - Class to check whether the helper methods responsible for creating test data work correctly. - """ - - def test_create_test_contract(self): - contract = self.__create_test_contract() - db_contract = Contract.objects.filter(id=contract.uuid).first() - self.assertEqual(db_contract, contract, "Failed to create contract in helper") - - def test_create_test_contract_custom(self): - contract = self.__create_test_contract(custom=True) - db_contract = Contract.objects.filter(id=contract.uuid).first() - params = self.__custom_contract_params - self.assertEqual(db_contract.version, params['version']) - self.assertEqual(db_contract.policy_holder, params['policy_holder']) - self.assertEqual(db_contract.state, params['state']) - - def test_create_test_contract_details(self): - contract_details = self.__create_test_contract_details() - db_contract_details = ContractDetails.objects.filter(id=contract_details.uuid).first() - self.assertEqual(db_contract_details, contract_details, "Failed to create contract details in helper") - - def test_create_test_contract_details_custom(self): - contract_details = self.__create_test_contract_details(custom=True) - db_contract_details = ContractDetails.objects.filter(id=contract_details.uuid).first() - params = self.__custom_contract_details_params - - self.assertEqual(db_contract_details.contract, params['contract']) - self.assertEqual(db_contract_details.insuree, params['insuree']) - self.assertEqual(db_contract_details.contribution_plan_bundle, params['contribution_plan_bundle']) - - def test_create_test_contract_contribution_plan_details(self): - contract_contribution_plan_details = self.__create_test_contract_contribution_plan_details() - db_contract_contribution_plan_details = ContractContributionPlanDetails.objects\ - .filter(id=contract_contribution_plan_details.uuid).first() - self.assertEqual(db_contract_contribution_plan_details, - contract_contribution_plan_details, - "Failed to create contract contribution plan details in helper") - - def test_create_test_contract_contribution_plan_details_custom(self): - contract_contribution_plan_details = self.__create_test_contract_contribution_plan_details(custom=True) - db_contract_contribution_plan_details = ContractContributionPlanDetails.objects\ - .filter(id=contract_contribution_plan_details.uuid).first() - params = self.__custom_contract_contribution_plan_details_params - self.assertEqual(db_contract_contribution_plan_details.contribution_plan, params['contribution_plan']) - self.assertEqual(db_contract_contribution_plan_details.contract_details, params['contract_details']) - - @property - @lru_cache(maxsize=2) - def __custom_contract_params(self): - return { - 'version': 2, - 'policy_holder': create_test_policy_holder(custom_props={'version': 2}), - 'state': 1, - } - - @property - @lru_cache(maxsize=2) - def __custom_contract_details_params(self): - return { - 'contract': self.__create_test_contract(True), - 'contribution_plan_bundle': create_test_contribution_plan_bundle(custom_props={'version': 3}), - 'insuree': create_test_insuree() - } - - @property - @lru_cache(maxsize=2) - def __custom_contract_contribution_plan_details_params(self): - return { - 'contribution_plan': create_test_contribution_plan(custom_props={'version': 2}), - 'contract_details': self.__create_test_contract_details(True), - } - - def __create_test_instance(self, function, **kwargs): - if kwargs: - return function(**kwargs) - else: - return function() - - def __create_test_contract(self, custom=False): - custom_params = self.__custom_contract_params if custom else {} - return self.__create_test_instance(create_test_contract, custom_props=custom_params) - - def __create_test_contract_details(self, custom=False): - custom_params = self.__custom_contract_details_params if custom else {} - return self.__create_test_instance(create_test_contract_details, custom_props=custom_params) - - def __create_test_contract_contribution_plan_details(self, custom=False): - custom_params = self.__custom_contract_contribution_plan_details_params if custom else {} - return self.__create_test_instance(create_test_contract_contribution_plan_details, - custom_props=custom_params) diff --git a/contract/tests/services/__init__.py b/contract/tests/services/__init__.py deleted file mode 100644 index e1875f5..0000000 --- a/contract/tests/services/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .services_tests import * \ No newline at end of file diff --git a/contract/tests/test_helpers.py b/contract/tests/test_helpers.py new file mode 100644 index 0000000..e7e48a4 --- /dev/null +++ b/contract/tests/test_helpers.py @@ -0,0 +1,158 @@ +from functools import lru_cache + +from django.test import TestCase + +from ..models import ( + ContractContributionPlanDetails, + Contract, + ContractDetails, +) +from .helpers import ( + create_test_contract_contribution_plan_details, + create_test_contract_details, + create_test_contract, +) +from insuree.test_helpers import ( + create_test_insuree, +) +from policyholder.tests.helpers import ( + create_test_policy_holder +) +from contribution_plan.tests.helpers import ( + create_test_contribution_plan_bundle +) +from contribution_plan.tests.helpers import create_test_contribution_plan + + +class HelpersTest(TestCase): + """ + Class to check whether the helper methods responsible for creating test data work correctly. + """ + + def test_create_test_contract(self): + contract = self.__create_test_contract() + db_contract = Contract.objects.filter(id=contract.uuid).first() + self.assertEqual(db_contract, contract, "Failed to create contract in helper") + + def test_create_test_contract_custom(self): + contract = self.__create_test_contract(custom=True) + db_contract = Contract.objects.filter(id=contract.uuid).first() + params = self.__custom_contract_params + self.assertEqual(db_contract.version, params["version"]) + self.assertEqual(db_contract.policy_holder, params["policy_holder"]) + self.assertEqual(db_contract.state, params["state"]) + + def test_create_test_contract_details(self): + contract_details = self.__create_test_contract_details() + db_contract_details = ContractDetails.objects.filter( + id=contract_details.uuid + ).first() + self.assertEqual( + db_contract_details, + contract_details, + "Failed to create contract details in helper", + ) + + def test_create_test_contract_details_custom(self): + contract_details = self.__create_test_contract_details(custom=True) + db_contract_details = ContractDetails.objects.filter( + id=contract_details.uuid + ).first() + params = self.__custom_contract_details_params + + self.assertEqual(db_contract_details.contract, params["contract"]) + self.assertEqual(db_contract_details.insuree, params["insuree"]) + self.assertEqual( + db_contract_details.contribution_plan_bundle, + params["contribution_plan_bundle"], + ) + + def test_create_test_contract_contribution_plan_details(self): + contract_contribution_plan_details = ( + self.__create_test_contract_contribution_plan_details() + ) + db_contract_contribution_plan_details = ( + ContractContributionPlanDetails.objects.filter( + id=contract_contribution_plan_details.uuid + ).first() + ) + self.assertEqual( + db_contract_contribution_plan_details, + contract_contribution_plan_details, + "Failed to create contract contribution plan details in helper", + ) + + def test_create_test_contract_contribution_plan_details_custom(self): + contract_contribution_plan_details = ( + self.__create_test_contract_contribution_plan_details(custom=True) + ) + db_contract_contribution_plan_details = ( + ContractContributionPlanDetails.objects.filter( + id=contract_contribution_plan_details.uuid + ).first() + ) + params = self.__custom_contract_contribution_plan_details_params + self.assertEqual( + db_contract_contribution_plan_details.contribution_plan, + params["contribution_plan"], + ) + self.assertEqual( + db_contract_contribution_plan_details.contract_details, + params["contract_details"], + ) + + @property + @lru_cache(maxsize=2) + def __custom_contract_params(self): + return { + "version": 2, + "policy_holder": create_test_policy_holder(custom_props={"version": 2}), + "state": 1, + } + + @property + @lru_cache(maxsize=2) + def __custom_contract_details_params(self): + return { + "contract": self.__create_test_contract(True), + "contribution_plan_bundle": create_test_contribution_plan_bundle( + custom_props={"version": 3} + ), + "insuree": create_test_insuree(), + } + + @property + @lru_cache(maxsize=2) + def __custom_contract_contribution_plan_details_params(self): + return { + "contribution_plan": create_test_contribution_plan( + custom_props={"version": 2} + ), + "contract_details": self.__create_test_contract_details(True), + } + + def __create_test_instance(self, function, **kwargs): + if kwargs: + return function(**kwargs) + else: + return function() + + def __create_test_contract(self, custom=False): + custom_params = self.__custom_contract_params if custom else {} + return self.__create_test_instance( + create_test_contract, custom_props=custom_params + ) + + def __create_test_contract_details(self, custom=False): + custom_params = self.__custom_contract_details_params if custom else {} + return self.__create_test_instance( + create_test_contract_details, custom_props=custom_params + ) + + def __create_test_contract_contribution_plan_details(self, custom=False): + custom_params = ( + self.__custom_contract_contribution_plan_details_params if custom else {} + ) + return self.__create_test_instance( + create_test_contract_contribution_plan_details, custom_props=custom_params + ) diff --git a/contract/tests/gql_tests/mutation_create_tests.py b/contract/tests/test_mutation_create_tests.py similarity index 62% rename from contract/tests/gql_tests/mutation_create_tests.py rename to contract/tests/test_mutation_create_tests.py index 060be53..d7b2581 100644 --- a/contract/tests/gql_tests/mutation_create_tests.py +++ b/contract/tests/test_mutation_create_tests.py @@ -1,38 +1,37 @@ import base64 +import datetime +import json +import uuid from unittest import mock -from django.test import TestCase import graphene -from contract.tests.helpers import ( - create_test_contract, - +from contribution_plan.tests.helpers import ( + create_test_contribution_plan, + create_test_contribution_plan_bundle, + create_test_contribution_plan_bundle_details, ) -from policy.test_helpers import create_test_policy -from contract.models import Contract, ContractDetails -from core.models import TechnicalUser, User +from core.models import User from core.models.openimis_graphql_test_case import openIMISGraphQLTestCase from core.test_helpers import create_test_interactive_user +from django.conf import settings +from graphene import Schema +from graphene.test import Client +from graphql_jwt.shortcuts import get_token +from policy.test_helpers import create_test_policy from policyholder.tests.helpers import ( create_test_policy_holder, - create_test_policy_holder_insuree -) -from contribution_plan.tests.helpers import ( - create_test_contribution_plan, - create_test_contribution_plan_bundle, - create_test_contribution_plan_bundle_details + create_test_policy_holder_insuree, ) from contract import schema as contract_schema -from graphene import Schema -from graphene.test import Client -from graphene_django.utils.testing import GraphQLTestCase -from django.conf import settings -import json -import uuid -from graphql_jwt.shortcuts import get_token -import datetime +from contract.models import Contract, ContractContributionPlanDetails +from contract.services import subtract_date_ranges +from contract.signals import append_contract_filter +from payment.models import Payment +from insuree.models import InsureePolicy + class MutationTestContract(openIMISGraphQLTestCase): - GRAPHQL_URL = f'/{settings.SITE_ROOT()}graphql' + GRAPHQL_URL = f"/{settings.SITE_ROOT()}graphql" # This is required by some version of graphene but is never used. It should be set to the schema but the import # is shown as an error in the IDE, so leaving it as True. GRAPHQL_SCHEMA = True @@ -49,9 +48,9 @@ class AnonymousUserContext: @classmethod def setUpClass(cls): super(MutationTestContract, cls).setUpClass() - cls.user = User.objects.filter(username='admin', i_user__isnull=False).first() + cls.user = User.objects.filter(username="admin", i_user__isnull=False).first() if not cls.user: - cls.user = create_test_interactive_user(username='admin') + cls.user = create_test_interactive_user(username="admin") # some test data so as to created contract properly cls.user_token = get_token(cls.user, cls.BaseTestContext(user=cls.user)) cls.income = 500 @@ -78,35 +77,40 @@ def setUpClass(cls): contribution_plan_bundle=cls.contribution_plan_bundle, custom_props={ "last_policy": None, - "json_ext": {"calculation_rule": {"income": cls.income}} - } + "json_ext": {"calculation_rule": {"income": cls.income}}, + }, ) create_test_policy( cls.contribution_plan.benefit_plan, ph_insuree.insuree, custom_props={ "start_date": datetime.datetime(2016, 3, 1), - "expiry_date": datetime.datetime(2021, 7, 1) - } + "expiry_date": datetime.datetime(2021, 7, 1), + }, ) cls.policy_holder_insuree = create_test_policy_holder_insuree( policy_holder=cls.policy_holder, - contribution_plan_bundle=cls.contribution_plan_bundle + contribution_plan_bundle=cls.contribution_plan_bundle, + custom_props={ + "json_ext": {"calculation_rule": {"income": cls.income}}, + } ) cls.policy_holder_insuree2 = create_test_policy_holder_insuree( policy_holder=cls.policy_holder, - contribution_plan_bundle=cls.contribution_plan_bundle + contribution_plan_bundle=cls.contribution_plan_bundle, + custom_props={ + "json_ext": {"calculation_rule": {"income": cls.income}}, + } ) cls.schema = Schema( - query=contract_schema.Query, - mutation=contract_schema.Mutation + query=contract_schema.Query, mutation=contract_schema.Mutation ) cls.graph_client = Client(cls.schema) def test_mutation_contract_create_without_policy_holder(self): - + input_param = { "code": "XYZ:" + str(self.time_stamp), "dateValidFrom": self.date_from, @@ -117,17 +121,10 @@ def test_mutation_contract_create_without_policy_holder(self): "contract", params=input_param, )["edges"] - #converted_id = base64.b64decode(result[0]['node']['id']).decode('utf-8').split(':')[1] + # converted_id = base64.b64decode(result[0]['node']['id']).decode('utf-8').split(':')[1] # tear down the test data - #Contract.objects.filter(id=str(converted_id)).delete() - self.assertEqual( - ( - "XYZ:" + str(self.time_stamp), - ), - ( - result[0]['node']['code'], - ) - ) + # Contract.objects.filter(id=str(converted_id)).delete() + self.assertEqual(("XYZ:" + str(self.time_stamp),), (result[0]["node"]["code"],)) def test_mutation_contract_create_with_policy_holder(self): input_param = { @@ -137,46 +134,86 @@ def test_mutation_contract_create_with_policy_holder(self): "dateValidFrom": self.date_from, "dateValidTo": self.date_to, } - content = self.send_mutation("createContract", input_param, self.user_token) - self.assertEqual(content['data']['mutationLogs']['edges'][0]['node']['status'], 2) + content = self.send_mutation("createContract", input_param, self.user_token) + self.assertEqual( + content["data"]["mutationLogs"]["edges"][0]["node"]["status"], 2 + ) del input_param["clientMutationId"] result = self.find_by_exact_attributes_query( "contract", params=input_param, )["edges"] - converted_id = base64.b64decode(result[0]['node']['id']).decode('utf-8').split(':')[1] + converted_id = ( + base64.b64decode(result[0]["node"]["id"]).decode("utf-8").split(":")[1] + ) # SUBMIT - input_param = {'id': converted_id, - "clientMutationId": str(uuid.uuid4())} - content = self.send_mutation("submitContract", input_param, self.user_token) - content = self.assertEqual(content['data']['mutationLogs']['edges'][0]['node']['status'], 2) + input_param = {"id": converted_id, "clientMutationId": str(uuid.uuid4())} + content = self.send_mutation("submitContract", input_param, self.user_token) + content = self.assertEqual( + content["data"]["mutationLogs"]["edges"][0]["node"]["status"], 2 + ) # COUNTER - input_param = {'id': converted_id, - "clientMutationId": str(uuid.uuid4())} - content = self.send_mutation("counterContract", input_param, self.user_token) + input_param = {"id": converted_id, "clientMutationId": str(uuid.uuid4())} + content = self.send_mutation("counterContract", input_param, self.user_token) - content = self.assertEqual(content['data']['mutationLogs']['edges'][0]['node']['status'], 2) + content = self.assertEqual( + content["data"]["mutationLogs"]["edges"][0]["node"]["status"], 2 + ) # reSUBMIT - input_param = {'id': converted_id, - "clientMutationId": str(uuid.uuid4())} - content = self.send_mutation("submitContract", input_param, self.user_token) - self.assertEqual(content['data']['mutationLogs']['edges'][0]['node']['status'], 2) + input_param = {"id": converted_id, "clientMutationId": str(uuid.uuid4())} + content = self.send_mutation("submitContract", input_param, self.user_token) + self.assertEqual( + content["data"]["mutationLogs"]["edges"][0]["node"]["status"], 2 + ) # Approve - input_param = {'id': converted_id, - "clientMutationId": str(uuid.uuid4())} - content = self.send_mutation("approveContract", input_param, self.user_token) + input_param = {"id": converted_id, "clientMutationId": str(uuid.uuid4())} + content = self.send_mutation("approveContract", input_param, self.user_token) - self.assertEqual(content['data']['mutationLogs']['edges'][0]['node']['status'], 2) - - # tear down the test data (TODO FK conflict with mutation ) - #ContractDetails.objects.filter(contract_id=str(converted_id)).delete() - #Contract.objects.filter(id=str(converted_id)).delete() + self.assertEqual( + content["data"]["mutationLogs"]["edges"][0]["node"]["status"], 2 + ) + # Pay + contract = Contract.objects.get(id=converted_id) + payment = Payment.objects.filter( + append_contract_filter( + None, + user=self.user, + additional_filter={ + 'contract': contract.id + } + ) + ).first() + input_param = { + "uuid": payment.uuid, + "clientMutationId": str(uuid.uuid4()), + "receivedAmount": str(payment.expected_amount), + "expectedAmount": str(payment.expected_amount), + "receiptNo": "tests", + "typeOfPayment": "C", + } + content = self.send_mutation("updatePayment", input_param, self.user_token) + self.assertEqual( + content["data"]["mutationLogs"]["edges"][0]["node"]["status"], 2 + ) + contract.refresh_from_db() + self.assertEqual(contract.state, Contract.STATE_EFFECTIVE, 'contract not effective') + insuree_policies = list(InsureePolicy.objects.filter(policy__in=ContractContributionPlanDetails.objects.filter( + contract_details__contract=contract + ).values_list('policy_id', flat=True))) + for d in list(contract.contractdetails_set.all()): + ips = [ip for ip in insuree_policies if ip.insuree_id == d.insuree_id] + self.assertTrue(len(ips) > 0) + not_covered = subtract_date_ranges( + (contract.date_valid_from, contract.date_valid_to,), + [(ip.effective_date, ip.expiry_date,) for ip in ips] + ) + self.assertTrue(not_covered == []) def find_by_id_query(self, query_type, id, context=None): - query = F''' + query = f""" {{ {query_type}(id:"{id}") {{ totalCount @@ -189,36 +226,37 @@ def find_by_id_query(self, query_type, id, context=None): }} }} }} - ''' + """ query_result = self.execute_query(query, context=context) - records = query_result[query_type]['edges'] + records = query_result[query_type]["edges"] if len(records) > 1: - raise ValueError(F"Ambiguous id {id} for query {query_type}") + raise ValueError(f"Ambiguous id {id} for query {query_type}") return records def find_by_exact_attributes_query(self, query_type, params, context=None): if "dateValidFrom" in params: - params.pop('dateValidFrom') + params.pop("dateValidFrom") if "clientMutationId" in params: - params.pop('clientMutationId') + params.pop("clientMutationId") if "dateValidTo" in params: - params.pop('dateValidTo') + params.pop("dateValidTo") if "policyHolderId" in params: - params.pop('policyHolderId') + params.pop("policyHolderId") if "contributionPlanBundleId" in params: - params.pop('contributionPlanBundleId') + params.pop("contributionPlanBundleId") if "insureeId" in params: - params.pop('insureeId') + params.pop("insureeId") if "uuids" in params: params["id"] = params["uuids"][0] params.pop("uuids") - node_content_str = "\n".join(params.keys()) if query_type == "contract" else '' - query = F''' + node_content_str = "\n".join(params.keys()) if query_type == "contract" else "" + query = f""" {{ - {query_type}({'contract_Id: "' + str(params["contractId"]) + '", orderBy: ["-dateCreated"]' if "contractId" in params else self.build_params(params)}) {{ + {query_type}({'contract_Id: "' + str(params["contractId"]) + + '", orderBy: ["-dateCreated"]' if "contractId" in params else self.build_params(params)}) {{ totalCount edges {{ node {{ @@ -235,7 +273,7 @@ def find_by_exact_attributes_query(self, query_type, params, context=None): }} }} }} - ''' + """ query_result = self.execute_query(query, context=context) records = query_result[query_type] return records @@ -245,29 +283,30 @@ def execute_query(self, query, context=None): context = self.BaseTestContext(self.user) query_result = self.graph_client.execute(query, context=context) - query_data = query_result['data'] + query_data = query_result["data"] return query_data def add_mutation(self, mutation_type, input_params, context=None): - + if "clientMutationId" not in input_params: - input_params["clientMutationId"]= str(uuid.uuid4()) - mutation = f''' - mutation + input_params["clientMutationId"] = str(uuid.uuid4()) + mutation = f""" + mutation {{ {mutation_type}(input: {{ {self.build_params(input_params)} - }}) + }}) {{ internalId clientMutationId }} }} - ''' - mutation_result = self.query(mutation, - - headers={"HTTP_AUTHORIZATION": f"Bearer {self.user_token}"}, ) + """ + mutation_result = self.query( + mutation, + headers={"HTTP_AUTHORIZATION": f"Bearer {self.user_token}"}, + ) self.assertResponseNoErrors(mutation_result) content = json.loads(mutation_result.content) return content @@ -282,15 +321,16 @@ def execute_mutation(self, mutation, context=None): def build_params(self, params): def wrap_arg(v): if isinstance(v, str): - return F'"{v}"' + return f'"{v}"' if isinstance(v, list): return json.dumps(v) if isinstance(v, bool): return str(v).lower() if isinstance(v, datetime.date): return graphene.DateTime.serialize( - datetime.datetime.fromordinal(v.toordinal())) + datetime.datetime.fromordinal(v.toordinal()) + ) return v - params_as_args = [f'{k}:{wrap_arg(v)}' for k, v in params.items()] + params_as_args = [f"{k}:{wrap_arg(v)}" for k, v in params.items()] return ", ".join(params_as_args) diff --git a/contract/tests/services/services_tests.py b/contract/tests/test_services.py similarity index 64% rename from contract/tests/services/services_tests.py rename to contract/tests/test_services.py index 288e7e7..f5e8bce 100644 --- a/contract/tests/services/services_tests.py +++ b/contract/tests/test_services.py @@ -1,15 +1,32 @@ -from django.test import TestCase -from contract.services import Contract as ContractService, ContractDetails as ContractDetailsService, \ - ContractContributionPlanDetails as ContractContributionPlanDetailsService, subtract_date_ranges -from contract.models import Contract, ContractDetails, ContractContributionPlanDetails +import datetime + +from calculation.services import ( + get_linked_class, + get_parameters, + get_rule_details, + get_rule_name, +) +from contribution_plan.tests.helpers import ( + create_test_contribution_plan, + create_test_contribution_plan_bundle, + create_test_contribution_plan_bundle_details, +) +from core.models import User from core.test_helpers import create_test_interactive_user -from policyholder.tests.helpers import create_test_policy_holder, create_test_policy_holder_insuree -from contribution_plan.tests.helpers import create_test_contribution_plan, \ - create_test_contribution_plan_bundle, create_test_contribution_plan_bundle_details +from django.test import TestCase from policy.test_helpers import create_test_policy -from core.models import User -from calculation.services import get_parameters, get_rule_details, get_rule_name, get_linked_class -import datetime +from policyholder.tests.helpers import ( + create_test_policy_holder, + create_test_policy_holder_insuree, +) + +from contract.models import Contract, ContractContributionPlanDetails, ContractDetails +from contract.services import Contract as ContractService +from contract.services import ( + ContractContributionPlanDetails as ContractContributionPlanDetailsService, +) +from contract.services import ContractDetails as ContractDetailsService +from contract.services import subtract_date_ranges class ServiceTestContract(TestCase): @@ -18,12 +35,16 @@ class ServiceTestContract(TestCase): @classmethod def setUpClass(cls): super(ServiceTestContract, cls).setUpClass() - cls.user = User.objects.filter(username='admin').first() + cls.user = User.objects.filter(username="admin").first() if not cls.user: - cls.user = create_test_interactive_user(username='admin', password='S\/pe®Pąßw0rd™') + cls.user = create_test_interactive_user( + username="admin", password="S/pe®Pąßw0rd™" + ) cls.contract_service = ContractService(cls.user) cls.contract_details_service = ContractDetailsService(cls.user) - cls.contract_contribution_plan_details_service = ContractContributionPlanDetailsService(cls.user) + cls.contract_contribution_plan_details_service = ( + ContractContributionPlanDetailsService(cls.user) + ) # some test data so as to created contract properly cls.income = 500 cls.rate = 5 @@ -38,9 +59,11 @@ def setUpClass(cls): cls.contribution_plan = create_test_contribution_plan( custom_props={"json_ext": {"calculation_rule": {"rate": cls.rate}}} ) - cls.contribution_plan_bundle_details = create_test_contribution_plan_bundle_details( - contribution_plan=cls.contribution_plan, - contribution_plan_bundle=cls.contribution_plan_bundle + cls.contribution_plan_bundle_details = ( + create_test_contribution_plan_bundle_details( + contribution_plan=cls.contribution_plan, + contribution_plan_bundle=cls.contribution_plan_bundle, + ) ) # create policy holder insuree for that test policy holder for i in range(0, cls.number_of_insuree): @@ -49,16 +72,16 @@ def setUpClass(cls): contribution_plan_bundle=cls.contribution_plan_bundle, custom_props={ "last_policy": None, - "json_ext": {"calculation_rule": {"income": cls.income}} - } + "json_ext": {"calculation_rule": {"income": cls.income}}, + }, ) create_test_policy( cls.contribution_plan.benefit_plan, ph_insuree.insuree, custom_props={ "start_date": datetime.datetime(2016, 3, 1), - "expiry_date": datetime.datetime(2021, 7, 1) - } + "expiry_date": datetime.datetime(2021, 7, 1), + }, ) def test_contract_create_without_policy_holder(self): @@ -69,7 +92,7 @@ def test_contract_create_without_policy_holder(self): } response = self.contract_service.create(contract) # tear down the test data - #Contract.objects.filter(id=response["data"]["id"]).delete() + # Contract.objects.filter(id=response["data"]["id"]).delete() self.assertEqual( ( True, @@ -80,26 +103,26 @@ def test_contract_create_without_policy_holder(self): None, ), ( - response['success'], - response['message'], - response['detail'], - response['data']['code'], - response['data']['amendment'], - response['data']['amount_notified'], - ) + response["success"], + response["message"], + response["detail"], + response["data"]["code"], + response["data"]["amendment"], + response["data"]["amount_notified"], + ), ) def test_contract_create_with_policy_holder(self): contract = { - "code": 'TESTONE', - 'policy_holder_id': self.policy_holder.id, + "code": "TESTONE", + "policy_holder_id": self.policy_holder.id, "date_valid_from": self.date_from, "date_valid_to": self.date_to, } response = self.contract_service.create(contract) # tear down the test data - #ContractDetails.objects.filter(contract_id=response["data"]["id"]).delete() - #Contract.objects.filter(id=response["data"]["id"]).delete() + # ContractDetails.objects.filter(contract_id=response["data"]["id"]).delete() + # Contract.objects.filter(id=response["data"]["id"]).delete() self.assertEqual( ( True, @@ -109,18 +132,18 @@ def test_contract_create_with_policy_holder(self): 0, ), ( - response['success'], - response['message'], - response['detail'], - response['data']['code'], - response['data']['amendment'], - ) + response["success"], + response["message"], + response["detail"], + response["data"]["code"], + response["data"]["amendment"], + ), ) def test_contract_create_update_delete_with_policy_holder(self): contract = { - "code": 'CTSV', - 'policy_holder_id': self.policy_holder.id, + "code": "CTSV", + "policy_holder_id": self.policy_holder.id, "date_valid_from": self.date_from, "date_valid_to": self.date_to, } @@ -134,27 +157,26 @@ def test_contract_create_update_delete_with_policy_holder(self): "date_valid_to": self.date_to, } response = self.contract_service.update(contract) - updated_payment_reference = response['data']['payment_reference'] + updated_payment_reference = response["data"]["payment_reference"] contract = { "id": contract_id, } response = self.contract_service.delete(contract) - is_deleted = response['success'] + is_deleted = response["success"] # tear down the test data - #ContractDetails.objects.filter(contract_id=contract_id).delete() - #Contract.objects.filter(id=contract_id).delete() + # ContractDetails.objects.filter(contract_id=contract_id).delete() + # Contract.objects.filter(id=contract_id).delete() self.assertEqual( - ("payment_one xxxxxxxx", True), - (updated_payment_reference, is_deleted) + ("payment_one xxxxxxxx", True), (updated_payment_reference, is_deleted) ) def test_contract_create_update_failed_ph(self): contract = { - "code": 'CSTG', - 'policy_holder_id': self.policy_holder.id, + "code": "CSTG", + "policy_holder_id": self.policy_holder.id, "date_valid_from": self.date_from, "date_valid_to": self.date_to, } @@ -167,24 +189,25 @@ def test_contract_create_update_failed_ph(self): "policy_holder_id": str(self.policy_holder2.id), } response = self.contract_service.update(contract) - failed = response['detail'] + failed = response["detail"] # tear down the test data - #ContractDetails.objects.filter(contract_id=contract_id).delete() - #Contract.objects.filter(id=contract_id).delete() + # ContractDetails.objects.filter(contract_id=contract_id).delete() + # Contract.objects.filter(id=contract_id).delete() self.assertEqual( - "ContractUpdateError: You cannot update already set PolicyHolder in Contract!", failed, + "ContractUpdateError: You cannot update already set PolicyHolder in Contract!", + failed, ) def test_contract_create_submit_fail_scenarios(self): contract = { - "code": 'MTD', - 'policy_holder_id': self.policy_holder.id, + "code": "MTD", + "policy_holder_id": self.policy_holder.id, "date_valid_from": self.date_from, "date_valid_to": self.date_to, } - + response = self.contract_service.create(contract) contract_id = str(response["data"]["id"]) @@ -205,31 +228,32 @@ def test_contract_create_submit_fail_scenarios(self): response = self.contract_service.submit(contract) result_message2 = response["detail"] - expected_message2 = "ContractUpdateError: The contract has been already submitted!" + expected_message2 = ( + "ContractUpdateError: The contract has been already submitted!" + ) contract_created.policy_holder = None contract_created.save(username="admin") response = self.contract_service.submit(contract) result_message3 = response["detail"] - expected_message3 = "ContractUpdateError: The contract does not contain PolicyHolder!" + expected_message3 = ( + "ContractUpdateError: The contract does not contain PolicyHolder!" + ) # tear down the test data - list_cd = list(ContractDetails.objects.filter(contract_id=contract_id).values('id', 'json_ext')) - #ContractDetails.objects.filter(contract_id=contract_id).delete() - #Contract.objects.filter(id=contract_id).delete() + list_cd = list( + ContractDetails.objects.filter(contract_id=contract_id).values( + "id", "json_ext" + ) + ) + self.assertNotEqual(list_cd, []) + # ContractDetails.objects.filter(contract_id=contract_id).delete() + # Contract.objects.filter(id=contract_id).delete() self.assertEqual( - ( - expected_message, - expected_message2, - expected_message3 - ), - ( - result_message, - result_message2, - result_message3 - ) + (expected_message, expected_message2, expected_message3), + (result_message, result_message2, result_message3), ) def test_contract_create_submit_counter(self): @@ -251,18 +275,19 @@ def test_contract_create_submit_counter(self): expected_state = 11 # tear down the test data - list_cd = list(ContractDetails.objects.filter(contract_id=contract_id).values('id')) - #for cd in list_cd: + list_cd = list( + ContractDetails.objects.filter(contract_id=contract_id).values("id") + ) + self.assertNotEqual(list_cd, []) + # for cd in list_cd: # ccpd = ContractContributionPlanDetails.objects.filter(contract_details__id=f"{cd['id']}").delete() - #ContractDetails.objects.filter(contract_id=contract_id).delete() - #Contract.objects.filter(id=contract_id).delete() + # ContractDetails.objects.filter(contract_id=contract_id).delete() + # Contract.objects.filter(id=contract_id).delete() - self.assertEqual( - expected_state, result_state - ) + self.assertEqual(expected_state, result_state) def test_contract_create_submit(self): - from core import datetime + contract = { "code": "TESTCON", "policy_holder_id": str(self.policy_holder.id), @@ -271,26 +296,33 @@ def test_contract_create_submit(self): } response = self.contract_service.create(contract) contract_id = str(response["data"]["id"]) - contract = {"id": contract_id, } + contract = { + "id": contract_id, + } response = self.contract_service.submit(contract) expected_state = 4 result_state = response["data"]["state"] # tear down the test data - list_cd = list(ContractDetails.objects.filter(contract_id=contract_id).values('id')) - for cd in list_cd: - ContractContributionPlanDetails.objects.filter(contract_details__id=f"{cd['id']}").delete() - #ContractDetails.objects.filter(contract_id=contract_id).delete() - #Contract.objects.filter(id=contract_id).delete() - self.assertEqual( - expected_state, result_state + list_cd = list( + ContractDetails.objects.filter(contract_id=contract_id).values("id") ) + for cd in list_cd: + ContractContributionPlanDetails.objects.filter( + contract_details__id=f"{cd['id']}" + ).delete() + # ContractDetails.objects.filter(contract_id=contract_id).delete() + # Contract.objects.filter(id=contract_id).delete() + self.assertEqual(expected_state, result_state) def test_contract_create_cd_from_phi(self): - from core import datetime + ph_insuree2 = create_test_policy_holder_insuree( policy_holder=self.policy_holder, contribution_plan_bundle=self.contribution_plan_bundle, - custom_props={"last_policy": None, "json_ext": {"calculation_rule": {"income": 400}}} + custom_props={ + "last_policy": None, + "json_ext": {"calculation_rule": {"income": 400}}, + }, ) contract = { "code": "MTEST-1", @@ -300,30 +332,30 @@ def test_contract_create_cd_from_phi(self): } response = self.contract_service.create(contract) contract_id = str(response["data"]["id"]) - contract = {"id": contract_id, } - ph_insuree_input = {"id": f'{ph_insuree2.id}', } - response = self.contract_details_service.ph_insuree_to_contract_details( - contract=contract, - ph_insuree=ph_insuree_input + contract = Contract.objects.get(id=contract_id) + + response = self.contract_details_service.get_details_from_ph_insuree( + contract=contract, ph_insuree=ph_insuree2.id ) # tear down the test data - #ContractDetails.objects.filter(contract_id=contract_id).delete() - #Contract.objects.filter(id=contract_id).delete() + # ContractDetails.objects.filter(contract_id=contract_id).delete() + # Contract.objects.filter(id=contract_id).delete() + + self.assertEqual(True, response["success"]) - self.assertEqual( - True, response["success"] - ) - def test_date_subtract(self): - date_range = (datetime.date(2024, 1, 1), datetime.date(2024, 12, 31),) + date_range = ( + datetime.date(2024, 1, 1), + datetime.date(2024, 12, 31), + ) date_ranges = [ (datetime.date(2024, 2, 1), datetime.date(2024, 3, 15)), (datetime.date(2024, 5, 1), datetime.date(2024, 6, 30)), - (datetime.date(2024, 8, 1), datetime.date(2024, 9, 30)) + (datetime.date(2024, 8, 1), datetime.date(2024, 9, 30)), ] result = subtract_date_ranges(date_range, date_ranges) - + per_1 = (datetime.date(2024, 1, 1), datetime.date(2024, 2, 1)) per_2 = (datetime.date(2024, 3, 15), datetime.date(2024, 5, 1)) per_3 = (datetime.date(2024, 6, 30), datetime.date(2024, 8, 1)) @@ -335,16 +367,17 @@ def test_date_subtract(self): self.assertTrue(per_4 in result) - class CalculationContractTest(TestCase): user = None @classmethod def setUpClass(cls): super(CalculationContractTest, cls).setUpClass() - cls.user = User.objects.filter(username='admin').first() + cls.user = User.objects.filter(username="admin").first() if not cls.user: - cls.user = create_test_interactive_user(username='admin', password='S\/pe®Pąßw0rd™') + cls.user = create_test_interactive_user( + username="admin", password="S/pe®Pąßw0rd™" + ) cls.contract_service = ContractService(cls.user) cls.income = 500 cls.rate = 5 @@ -358,9 +391,11 @@ def setUpClass(cls): cls.contribution_plan = create_test_contribution_plan( custom_props={"json_ext": {"calculation_rule": {"rate": cls.rate}}} ) - cls.contribution_plan_bundle_details = create_test_contribution_plan_bundle_details( - contribution_plan=cls.contribution_plan, - contribution_plan_bundle=cls.contribution_plan_bundle + cls.contribution_plan_bundle_details = ( + create_test_contribution_plan_bundle_details( + contribution_plan=cls.contribution_plan, + contribution_plan_bundle=cls.contribution_plan_bundle, + ) ) # create policy holder insuree for that test policy holder @@ -368,7 +403,10 @@ def setUpClass(cls): create_test_policy_holder_insuree( policy_holder=cls.policy_holder, contribution_plan_bundle=cls.contribution_plan_bundle, - custom_props={"last_policy": None, "json_ext": {"calculation_rule": {"income": cls.income}}} + custom_props={ + "last_policy": None, + "json_ext": {"calculation_rule": {"income": cls.income}}, + }, ) def test_get_rule_name(self): @@ -386,11 +424,16 @@ def test_get_rule_details(self): class_name2 = "ContributionPlan" result = get_rule_details(class_name=class_name) result2 = get_rule_details(class_name=class_name2) - result_param = [param['name'] for param in result[class_name]] - result2_param = [param['name'] for param in result2[class_name2]] + result_param = [param["name"] for param in result[class_name]] + result2_param = [param["name"] for param in result2[class_name2]] self.assertEqual( (class_name, class_name2, ["income"], ["rate", "includeFamily"]), - (list(result.keys())[0], list(result2.keys())[0], result_param, result2_param) + ( + list(result.keys())[0], + list(result2.keys())[0], + result_param, + result2_param, + ), ) def test_get_rule_details_not_existing(self): @@ -400,13 +443,21 @@ def test_get_rule_details_not_existing(self): def test_get_linked_class_empty(self): result = get_linked_class() - self.assertEqual(['Calculation'], result) + self.assertEqual(["Calculation"], result) def test_get_linked_class(self): result = get_linked_class(["PolicyHolderInsuree"]) self.assertEqual( - sorted(['PolicyHolder', 'Insuree', 'ContributionPlanBundle', 'Policy', 'Calculation']), - sorted(result) + sorted( + [ + "PolicyHolder", + "Insuree", + "ContributionPlanBundle", + "Policy", + "Calculation", + ] + ), + sorted(result), ) def test_get_param_and_amount_calculation(self): @@ -415,23 +466,23 @@ def test_get_param_and_amount_calculation(self): # and test if on instance of contract details the proper params is showed # by getting param name contract = { - 'code': 'CALTEST', - 'policy_holder_id': self.policy_holder.id, + "code": "CALTEST", + "policy_holder_id": self.policy_holder.id, "date_valid_from": self.date_from, "date_valid_to": self.date_to, } response = self.contract_service.create(contract) - self.assertTrue(response['success']) + self.assertTrue(response["success"]) # after creating contract - we can get contract details so as to get params # run calculation rules etc cd = ContractDetails.objects.filter(contract_id=response["data"]["id"]).first() - c = Contract.objects.filter(id=response["data"]["id"]).first() + # c = Contract.objects.filter(id=response["data"]["id"]).first() result_params = get_parameters("PolicyHolderInsuree", cd) # tear down the contract test data - #cd.delete() - #c.delete() + # cd.delete() + # c.delete() # we want to assert name of param related to the contract details (should be 'income') # and also the value of contract (all contributions) @@ -439,7 +490,7 @@ def test_get_param_and_amount_calculation(self): # income*rate*number of contributions = according to Contribution Valuation Rule self.assertEqual( ("income", self.income * (float(self.rate / 100)) * self.number_of_insuree), - (result_params[0]["name"], response["data"]["amount_notified"]) + (result_params[0]["name"], response["data"]["amount_notified"]), ) def test_no_deatils(self): @@ -448,13 +499,16 @@ def test_no_deatils(self): # and test if on instance of contract details the proper params is showed # by getting param name contract = { - 'code': 'CALTEST', - 'policy_holder_id': self.policy_holder.id, + "code": "CALTEST", + "policy_holder_id": self.policy_holder.id, "date_valid_from": datetime.date(2017, 1, 1), "date_valid_to": datetime.date(2017, 1, 31), } response = self.contract_service.create(contract) - self.assertTrue(response['success']) + self.assertTrue(response["success"]) cd = ContractDetails.objects.filter(contract_id=response["data"]["id"]).first() - self.assertIsNone(cd, 'the contract should not have any detail as there is no insuree valid at this time') \ No newline at end of file + self.assertIsNone( + cd, + "the contract should not have any detail as there is no insuree valid at this time", + ) diff --git a/contract/tests/gql_tests/query_tests.py b/contract/tests/tests_gql_query.py similarity index 69% rename from contract/tests/gql_tests/query_tests.py rename to contract/tests/tests_gql_query.py index eb883fd..6e10102 100644 --- a/contract/tests/gql_tests/query_tests.py +++ b/contract/tests/tests_gql_query.py @@ -1,14 +1,20 @@ import base64 +import datetime from unittest import mock -from django.test import TestCase from uuid import UUID import graphene -from contract.tests.helpers import * -from contract import schema as contract_schema +from django.test import TestCase from graphene import Schema from graphene.test import Client +from contract import schema as contract_schema +from contract.tests.helpers import ( + create_test_contract, + create_test_contract_contribution_plan_details, + create_test_contract_details, +) + class ContractQueryTest(TestCase): class BaseTestContext: @@ -24,18 +30,20 @@ def setUpClass(cls): cls.date_created = datetime.datetime.now() cls.test_contract = create_test_contract( custom_props={ - 'code': 'testContract-' + str(cls.date_created), - 'payment_reference': 'payment reference' + str(cls.date_created), - 'amount_due': 450.99, - 'date_valid_from': datetime.date(2020, 1, 1), - 'amendment': 1 + "code": "testContract-" + str(cls.date_created), + "payment_reference": "payment reference" + str(cls.date_created), + "amount_due": 450.99, + "date_valid_from": datetime.date(2020, 1, 1), + "amendment": 1, } ) cls.test_contract_details = create_test_contract_details( contract=cls.test_contract ) - cls.test_contract_contribution_plan_details = create_test_contract_contribution_plan_details( - contract_details=cls.test_contract_details + cls.test_contract_contribution_plan_details = ( + create_test_contract_contribution_plan_details( + contract_details=cls.test_contract_details + ) ) cls.schema = Schema( @@ -47,35 +55,41 @@ def setUpClass(cls): def test_find_contract_existing(self): id = self.test_contract.id result = self.find_by_id_query("contract", id) - converted_id = base64.b64decode(result[0]['node']['id']).decode('utf-8').split(':')[1] + converted_id = ( + base64.b64decode(result[0]["node"]["id"]).decode("utf-8").split(":")[1] + ) self.assertEqual(UUID(converted_id), id) def test_find_contract_details_existing(self): id = self.test_contract_details.id result = self.find_by_id_query("contractDetails", id) - converted_id = base64.b64decode(result[0]['node']['id']).decode('utf-8').split(':')[1] + converted_id = ( + base64.b64decode(result[0]["node"]["id"]).decode("utf-8").split(":")[1] + ) self.assertEqual(UUID(converted_id), id) def test_find_contract_contribution_plan_details_existing(self): id = self.test_contract_contribution_plan_details.id result = self.find_by_id_query("contractContributionPlanDetails", id) - converted_id = base64.b64decode(result[0]['node']['id']).decode('utf-8').split(':')[1] + converted_id = ( + base64.b64decode(result[0]["node"]["id"]).decode("utf-8").split(":")[1] + ) self.assertEqual(UUID(converted_id), id) def test_find_contract_by_params(self): expected = self.test_contract params = { - 'version': expected.version, - 'isDeleted': True if expected.is_deleted else False, - 'code': expected.code, + "version": expected.version, + "isDeleted": True if expected.is_deleted else False, + "code": expected.code, } result = self.find_by_exact_attributes_query("contract", params) - self.assertDictEqual(result[0]['node'], params) + self.assertDictEqual(result[0]["node"], params) def test_find_contract_details_by_contract(self): details_contract_id = self.test_contract_details.contract.id id = self.test_contract_details.id - query = F''' + query = f""" {{ contractDetails( contract_Id: "{details_contract_id}"){{ @@ -88,16 +102,16 @@ def test_find_contract_details_by_contract(self): }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contractDetails']['edges'][0]['node'] - converted_id = base64.b64decode(result['id']).decode('utf-8').split(':')[1] + result = query_result["contractDetails"]["edges"][0]["node"] + converted_id = base64.b64decode(result["id"]).decode("utf-8").split(":")[1] self.assertEqual(UUID(converted_id), id) def test_find_contract_details_by_id_and_insuree(self): details_insuree_uuid = self.test_contract_details.insuree.uuid id = self.test_contract_details.id - query = F''' + query = f""" {{ contractDetails( id: "{id}" @@ -111,16 +125,16 @@ def test_find_contract_details_by_id_and_insuree(self): }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contractDetails']['edges'][0]['node'] - converted_id = base64.b64decode(result['id']).decode('utf-8').split(':')[1] + result = query_result["contractDetails"]["edges"][0]["node"] + converted_id = base64.b64decode(result["id"]).decode("utf-8").split(":")[1] self.assertEqual(UUID(converted_id), id) def test_find_contract_by_contract_code(self): code = self.test_contract.code id = self.test_contract.id - query = F''' + query = f""" {{ contract( code: "{code}"){{ @@ -133,16 +147,16 @@ def test_find_contract_by_contract_code(self): }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contract']['edges'][0]['node'] - converted_id = base64.b64decode(result['id']).decode('utf-8').split(':')[1] + result = query_result["contract"]["edges"][0]["node"] + converted_id = base64.b64decode(result["id"]).decode("utf-8").split(":")[1] self.assertEqual(UUID(converted_id), id) def test_find_contract_by_contract_payment_reference(self): payment_reference = self.test_contract.payment_reference id = self.test_contract.id - query = F''' + query = f""" {{ contract( paymentReference: "{payment_reference}"){{ @@ -155,17 +169,17 @@ def test_find_contract_by_contract_payment_reference(self): }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contract']['edges'][0]['node'] - converted_id = base64.b64decode(result['id']).decode('utf-8').split(':')[1] + result = query_result["contract"]["edges"][0]["node"] + converted_id = base64.b64decode(result["id"]).decode("utf-8").split(":")[1] self.assertEqual(UUID(converted_id), id) def test_find_contract_by_contract_code_amount_due(self): code = self.test_contract.code amount_due = self.test_contract.amount_due id = self.test_contract.id - query = F''' + query = f""" {{ contract( code:" {code}", amountDue: {amount_due}){{ @@ -178,17 +192,17 @@ def test_find_contract_by_contract_code_amount_due(self): }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contract']['edges'][0]['node'] - converted_id = base64.b64decode(result['id']).decode('utf-8').split(':')[1] + result = query_result["contract"]["edges"][0]["node"] + converted_id = base64.b64decode(result["id"]).decode("utf-8").split(":")[1] self.assertEqual(UUID(converted_id), id) def test_find_contract_by_contract_code_amount_due_greater(self): code = self.test_contract.code amount_due = self.test_contract.amount_due - 100 id = self.test_contract.id - query = F''' + query = f""" {{ contract( code:" {code}", amountDue_Gte: {amount_due}){{ @@ -201,17 +215,17 @@ def test_find_contract_by_contract_code_amount_due_greater(self): }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contract']['edges'][0]['node'] - converted_id = base64.b64decode(result['id']).decode('utf-8').split(':')[1] + result = query_result["contract"]["edges"][0]["node"] + converted_id = base64.b64decode(result["id"]).decode("utf-8").split(":")[1] self.assertEqual(UUID(converted_id), id) def test_find_contract_by_contract_code_amount_due_greater_equal(self): code = self.test_contract.code amount_due = self.test_contract.amount_due id = self.test_contract.id - query = F''' + query = f""" {{ contract( code:" {code}", amountDue_Gte: {amount_due}){{ @@ -224,17 +238,17 @@ def test_find_contract_by_contract_code_amount_due_greater_equal(self): }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contract']['edges'][0]['node'] - converted_id = base64.b64decode(result['id']).decode('utf-8').split(':')[1] + result = query_result["contract"]["edges"][0]["node"] + converted_id = base64.b64decode(result["id"]).decode("utf-8").split(":")[1] self.assertEqual(UUID(converted_id), id) def test_find_contract_by_contract_code_amound_due_lt(self): code = self.test_contract.code amount_due = self.test_contract.amount_due - id = self.test_contract.id - query = F''' + # id = self.test_contract.id + query = f""" {{ contract( code:" {code}", amountDue_Lt: {amount_due}){{ @@ -247,15 +261,15 @@ def test_find_contract_by_contract_code_amound_due_lt(self): }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contract']['edges'] + result = query_result["contract"]["edges"] self.assertEqual(len(result), 0) def test_find_contract_by_insuree(self): details_insuree_uuid = self.test_contract_details.insuree.uuid id = self.test_contract.id - query = F''' + query = f""" {{ contract( insuree: "{details_insuree_uuid}") {{ @@ -268,16 +282,16 @@ def test_find_contract_by_insuree(self): }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contract']['edges'][0]['node'] - converted_id = base64.b64decode(result['id']).decode('utf-8').split(':')[1] + result = query_result["contract"]["edges"][0]["node"] + converted_id = base64.b64decode(result["id"]).decode("utf-8").split(":")[1] self.assertEqual(UUID(converted_id), id) def test_find_contract_by_id_and_state(self): details_contract_state = self.test_contract.state id = self.test_contract.id - query = F''' + query = f""" {{ contract( id: "{id}" @@ -291,17 +305,23 @@ def test_find_contract_by_id_and_state(self): }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contract']['edges'][0]['node'] - converted_id = base64.b64decode(result['id']).decode('utf-8').split(':')[1] + result = query_result["contract"]["edges"][0]["node"] + converted_id = base64.b64decode(result["id"]).decode("utf-8").split(":")[1] self.assertEqual(UUID(converted_id), id) - def test_find_contract_contribution_plan_details_by_contract_details_and_contribution_plan(self): - details_contract_details_id = self.test_contract_contribution_plan_details.contract_details.id - details_contribution_plan_id = self.test_contract_contribution_plan_details.contribution_plan.id + def test_find_contract_contribution_plan_details_by_contract_details_and_contribution_plan( + self, + ): + details_contract_details_id = ( + self.test_contract_contribution_plan_details.contract_details.id + ) + details_contribution_plan_id = ( + self.test_contract_contribution_plan_details.contribution_plan.id + ) id = self.test_contract_contribution_plan_details.id - query = F''' + query = f""" {{ contractContributionPlanDetails( contractDetails_Id: "{details_contract_details_id}", @@ -315,17 +335,17 @@ def test_find_contract_contribution_plan_details_by_contract_details_and_contrib }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contractContributionPlanDetails']['edges'][0]['node'] - converted_id = base64.b64decode(result['id']).decode('utf-8').split(':')[1] + result = query_result["contractContributionPlanDetails"]["edges"][0]["node"] + converted_id = base64.b64decode(result["id"]).decode("utf-8").split(":")[1] self.assertEqual(UUID(converted_id), id) def test_find_contract_by_contract_code_date_valid_from_gte(self): code = self.test_contract.code - date_valid_from = str(self.test_contract.date_valid_from.date()) + 'T00:00:00' + date_valid_from = str(self.test_contract.date_valid_from.date()) + "T00:00:00" id = self.test_contract.id - query = F''' + query = f""" {{ contract( code: "{code}", dateValidFrom_Gte: "{date_valid_from}"){{ @@ -338,16 +358,16 @@ def test_find_contract_by_contract_code_date_valid_from_gte(self): }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contract']['edges'][0]['node'] - converted_id = base64.b64decode(result['id']).decode('utf-8').split(':')[1] + result = query_result["contract"]["edges"][0]["node"] + converted_id = base64.b64decode(result["id"]).decode("utf-8").split(":")[1] self.assertEqual(UUID(converted_id), id) def test_find_contract_by_contract_code_contains(self): date = self.date_created id = self.test_contract.id - query = F''' + query = f""" {{ contract( code_Icontains: "{date}"){{ @@ -360,17 +380,17 @@ def test_find_contract_by_contract_code_contains(self): }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contract']['edges'][0]['node'] - converted_id = base64.b64decode(result['id']).decode('utf-8').split(':')[1] + result = query_result["contract"]["edges"][0]["node"] + converted_id = base64.b64decode(result["id"]).decode("utf-8").split(":")[1] self.assertEqual(UUID(converted_id), id) def test_find_contract_by_code_amendment(self): code = self.test_contract.code amendment = self.test_contract.amendment id = self.test_contract.id - query = F''' + query = f""" {{ contract( code: "{code}", amendment: {amendment}){{ @@ -383,17 +403,17 @@ def test_find_contract_by_code_amendment(self): }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contract']['edges'][0]['node'] - converted_id = base64.b64decode(result['id']).decode('utf-8').split(':')[1] + result = query_result["contract"]["edges"][0]["node"] + converted_id = base64.b64decode(result["id"]).decode("utf-8").split(":")[1] self.assertEqual(UUID(converted_id), id) def test_find_contract_by_contract_code_start_with(self): - query = F''' + query = f""" {{ contract( - code_Istartswith: "testContract-"){{ + code_Istartswith: "{'testContract-'}"){{ totalCount edges {{ node {{ @@ -403,15 +423,15 @@ def test_find_contract_by_contract_code_start_with(self): }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contract']['edges'] + result = query_result["contract"]["edges"] self.assertGreater(len(result), 0) def test_find_contracts_details_by_date_created(self): - date_created = str(self.test_contract_details.date_created).replace(' ', 'T') + date_created = str(self.test_contract_details.date_created).replace(" ", "T") id = self.test_contract_details.id - query = F''' + query = f""" {{ contractDetails( dateCreated: "{date_created}"){{ @@ -424,16 +444,16 @@ def test_find_contracts_details_by_date_created(self): }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contractDetails']['edges'][0]["node"] - converted_id = base64.b64decode(result['id']).decode('utf-8').split(':')[1] + result = query_result["contractDetails"]["edges"][0]["node"] + converted_id = base64.b64decode(result["id"]).decode("utf-8").split(":")[1] self.assertEqual(UUID(converted_id), id) def test_find_contracts_details_by_date_updated(self): - date_updated = str(self.test_contract_details.date_updated).replace(' ', 'T') + date_updated = str(self.test_contract_details.date_updated).replace(" ", "T") id = self.test_contract_details.id - query = F''' + query = f""" {{ contractDetails( dateUpdated: "{date_updated}"){{ @@ -446,16 +466,18 @@ def test_find_contracts_details_by_date_updated(self): }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contractDetails']['edges'][0]["node"] - converted_id = base64.b64decode(result['id']).decode('utf-8').split(':')[1] + result = query_result["contractDetails"]["edges"][0]["node"] + converted_id = base64.b64decode(result["id"]).decode("utf-8").split(":")[1] self.assertEqual(UUID(converted_id), id) def test_find_contract_contribution_plan_details_by_date_created(self): - date_created = str(self.test_contract_contribution_plan_details.date_created).replace(' ', 'T') + date_created = str( + self.test_contract_contribution_plan_details.date_created + ).replace(" ", "T") id = self.test_contract_contribution_plan_details.id - query = F''' + query = f""" {{ contractContributionPlanDetails( dateCreated: "{date_created}"){{ @@ -468,16 +490,18 @@ def test_find_contract_contribution_plan_details_by_date_created(self): }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contractContributionPlanDetails']['edges'][0]["node"] - converted_id = base64.b64decode(result['id']).decode('utf-8').split(':')[1] + result = query_result["contractContributionPlanDetails"]["edges"][0]["node"] + converted_id = base64.b64decode(result["id"]).decode("utf-8").split(":")[1] self.assertEqual(UUID(converted_id), id) def test_find_contract_contribution_plan_details_by_date_created_gte(self): - date_created = str(self.test_contract_contribution_plan_details.date_created).replace(' ', 'T') + date_created = str( + self.test_contract_contribution_plan_details.date_created + ).replace(" ", "T") id = self.test_contract_contribution_plan_details.id - query = F''' + query = f""" {{ contractContributionPlanDetails( dateCreated_Gte: "{date_created}"){{ @@ -490,16 +514,18 @@ def test_find_contract_contribution_plan_details_by_date_created_gte(self): }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contractContributionPlanDetails']['edges'][0]["node"] - converted_id = base64.b64decode(result['id']).decode('utf-8').split(':')[1] + result = query_result["contractContributionPlanDetails"]["edges"][0]["node"] + converted_id = base64.b64decode(result["id"]).decode("utf-8").split(":")[1] self.assertEqual(UUID(converted_id), id) def test_find_contract_contribution_plan_details_by_date_created_gt(self): - date_created = str(self.test_contract_contribution_plan_details.date_created).replace(' ', 'T') - id = self.test_contract_contribution_plan_details.id - query = F''' + date_created = str( + self.test_contract_contribution_plan_details.date_created + ).replace(" ", "T") + # id = self.test_contract_contribution_plan_details.id + query = f""" {{ contractContributionPlanDetails( dateCreated_Gt: "{date_created}"){{ @@ -512,15 +538,17 @@ def test_find_contract_contribution_plan_details_by_date_created_gt(self): }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contractContributionPlanDetails']['edges'] + result = query_result["contractContributionPlanDetails"]["edges"] self.assertEqual(len(result), 0) def test_find_contract_contribution_plan_details_by_date_updated(self): - date_updated = str(self.test_contract_contribution_plan_details.date_updated).replace(' ', 'T') + date_updated = str( + self.test_contract_contribution_plan_details.date_updated + ).replace(" ", "T") id = self.test_contract_contribution_plan_details.id - query = F''' + query = f""" {{ contractContributionPlanDetails( dateUpdated: "{date_updated}"){{ @@ -533,16 +561,18 @@ def test_find_contract_contribution_plan_details_by_date_updated(self): }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contractContributionPlanDetails']['edges'][0]["node"] - converted_id = base64.b64decode(result['id']).decode('utf-8').split(':')[1] + result = query_result["contractContributionPlanDetails"]["edges"][0]["node"] + converted_id = base64.b64decode(result["id"]).decode("utf-8").split(":")[1] self.assertEqual(UUID(converted_id), id) def test_find_contract_contribution_plan_details_by_id_and_contribution(self): - details_contribution_uuid = self.test_contract_contribution_plan_details.contribution.uuid + details_contribution_uuid = ( + self.test_contract_contribution_plan_details.contribution.uuid + ) id = self.test_contract_contribution_plan_details.id - query = F''' + query = f""" {{ contractContributionPlanDetails( id: "{id}" @@ -556,16 +586,20 @@ def test_find_contract_contribution_plan_details_by_id_and_contribution(self): }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contractContributionPlanDetails']['edges'][0]['node'] - converted_id = base64.b64decode(result['id']).decode('utf-8').split(':')[1] + result = query_result["contractContributionPlanDetails"]["edges"][0]["node"] + converted_id = base64.b64decode(result["id"]).decode("utf-8").split(":")[1] self.assertEqual(UUID(converted_id), id) - def test_find_contract_contribution_plan_details_by_id_and_amount_in_contribution_gte(self): - details_contribution_amount = self.test_contract_contribution_plan_details.contribution.amount + def test_find_contract_contribution_plan_details_by_id_and_amount_in_contribution_gte( + self, + ): + details_contribution_amount = ( + self.test_contract_contribution_plan_details.contribution.amount + ) id = self.test_contract_contribution_plan_details.id - query = F''' + query = f""" {{ contractContributionPlanDetails( id: "{id}" @@ -579,16 +613,20 @@ def test_find_contract_contribution_plan_details_by_id_and_amount_in_contributio }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contractContributionPlanDetails']['edges'][0]['node'] - converted_id = base64.b64decode(result['id']).decode('utf-8').split(':')[1] + result = query_result["contractContributionPlanDetails"]["edges"][0]["node"] + converted_id = base64.b64decode(result["id"]).decode("utf-8").split(":")[1] self.assertEqual(UUID(converted_id), id) - def test_find_contract_contribution_plan_details_by_id_and_amount_in_contribution_gt(self): - details_contribution_amount = self.test_contract_contribution_plan_details.contribution.amount + def test_find_contract_contribution_plan_details_by_id_and_amount_in_contribution_gt( + self, + ): + details_contribution_amount = ( + self.test_contract_contribution_plan_details.contribution.amount + ) id = self.test_contract_contribution_plan_details.id - query = F''' + query = f""" {{ contractContributionPlanDetails( id: "{id}" @@ -602,15 +640,15 @@ def test_find_contract_contribution_plan_details_by_id_and_amount_in_contributio }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contractContributionPlanDetails']['edges'] + result = query_result["contractContributionPlanDetails"]["edges"] self.assertEqual(len(result), 0) def test_find_contract_contribution_plan_details_by_insuree(self): details_insuree_uuid = self.test_contract_details.insuree.uuid id = self.test_contract_contribution_plan_details.id - query = F''' + query = f""" {{ contractContributionPlanDetails( insuree: "{details_insuree_uuid}") {{ @@ -623,14 +661,14 @@ def test_find_contract_contribution_plan_details_by_insuree(self): }} }} }} - ''' + """ query_result = self.execute_query(query) - result = query_result['contractContributionPlanDetails']['edges'][0]['node'] - converted_id = base64.b64decode(result['id']).decode('utf-8').split(':')[1] + result = query_result["contractContributionPlanDetails"]["edges"][0]["node"] + converted_id = base64.b64decode(result["id"]).decode("utf-8").split(":")[1] self.assertEqual(UUID(converted_id), id) def find_by_id_query(self, query_type, id, context=None): - query = F''' + query = f""" {{ {query_type}(id:"{id}") {{ totalCount @@ -642,19 +680,19 @@ def find_by_id_query(self, query_type, id, context=None): }} }} }} - ''' + """ query_result = self.execute_query(query, context=context) - records = query_result[query_type]['edges'] + records = query_result[query_type]["edges"] if len(records) > 1: - raise ValueError(F"Ambiguous id {id} for query {query_type}") + raise ValueError(f"Ambiguous id {id} for query {query_type}") return records def find_by_exact_attributes_query(self, query_type, params, context=None): node_content_str = "\n".join(params.keys()) - query = F''' + query = f""" {{ {query_type}({self.build_params(params)}) {{ totalCount @@ -666,9 +704,9 @@ def find_by_exact_attributes_query(self, query_type, params, context=None): }} }} }} - ''' + """ query_result = self.execute_query(query, context=context) - records = query_result[query_type]['edges'] + records = query_result[query_type]["edges"] return records def execute_query(self, query, context=None): @@ -676,19 +714,20 @@ def execute_query(self, query, context=None): context = self.BaseTestContext() query_result = self.graph_client.execute(query, context=context) - query_data = query_result['data'] + query_data = query_result["data"] return query_data def build_params(self, params): def wrap_arg(v): if isinstance(v, str): - return F'"{v}"' + return f'"{v}"' if isinstance(v, bool): return str(v).lower() if isinstance(v, datetime.date): return graphene.DateTime.serialize( - datetime.datetime.fromordinal(v.toordinal())) + datetime.datetime.fromordinal(v.toordinal()) + ) return v - params_as_args = [f'{k}:{wrap_arg(v)}' for k, v in params.items()] + params_as_args = [f"{k}:{wrap_arg(v)}" for k, v in params.items()] return ", ".join(params_as_args) diff --git a/contract/urls.py b/contract/urls.py index ff12309..637600f 100644 --- a/contract/urls.py +++ b/contract/urls.py @@ -1,2 +1 @@ - -urlpatterns = [] \ No newline at end of file +urlpatterns = [] diff --git a/contract/utils.py b/contract/utils.py index 17a4308..2817445 100644 --- a/contract/utils.py +++ b/contract/utils.py @@ -1,7 +1,7 @@ from django.db.models import Q -def filter_amount_contract(arg='amount_from', arg2='amount_to', **kwargs): +def filter_amount_contract(arg="amount_from", arg2="amount_to", **kwargs): amount_from = kwargs.get(arg) amount_to = kwargs.get(arg2) @@ -12,23 +12,35 @@ def filter_amount_contract(arg='amount_from', arg2='amount_to', **kwargs): # scenario - only amount_to set if not amount_from and amount_to: return ( - Q(amount_notified__lte=amount_to, state__in=status_notified) | - Q(amount_rectified__lte=amount_to, state__in=status_rectified) | - Q(amount_due__lte=amount_to, state__in=status_due) + Q(amount_notified__lte=amount_to, state__in=status_notified) + | Q(amount_rectified__lte=amount_to, state__in=status_rectified) + | Q(amount_due__lte=amount_to, state__in=status_due) ) # scenario - only amount_from set if amount_from and not amount_to: return ( - Q(amount_notified__gte=amount_from, state__in=status_notified) | - Q(amount_rectified__gte=amount_from, state__in=status_rectified) | - Q(amount_due__gte=amount_from, state__in=status_due) + Q(amount_notified__gte=amount_from, state__in=status_notified) + | Q(amount_rectified__gte=amount_from, state__in=status_rectified) + | Q(amount_due__gte=amount_from, state__in=status_due) ) # scenario - both filters set if amount_from and amount_to: return ( - Q(amount_notified__gte=amount_from, amount_notified__lte=amount_to, state__in=status_notified) | - Q(amount_rectified__gte=amount_from, amount_rectified__lte=amount_to, state__in=status_rectified) | - Q(amount_due__gte=amount_from, amount_due__lte=amount_to, state__in=status_due) + Q( + amount_notified__gte=amount_from, + amount_notified__lte=amount_to, + state__in=status_notified, + ) + | Q( + amount_rectified__gte=amount_from, + amount_rectified__lte=amount_to, + state__in=status_rectified, + ) + | Q( + amount_due__gte=amount_from, + amount_due__lte=amount_to, + state__in=status_due, + ) ) diff --git a/contract/views.py b/contract/views.py index 91ea44a..fd0e044 100644 --- a/contract/views.py +++ b/contract/views.py @@ -1,3 +1,3 @@ -from django.shortcuts import render +# from django.shortcuts import render # Create your views here. diff --git a/setup.py b/setup.py index 319eadf..dfadd17 100644 --- a/setup.py +++ b/setup.py @@ -1,47 +1,47 @@ - import os + from setuptools import find_packages, setup -with open(os.path.join(os.path.dirname(__file__), 'README.md')) as readme: +with open(os.path.join(os.path.dirname(__file__), "README.md")) as readme: README = readme.read() # allow setup.py to be run from any path os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) setup( - name='openimis-be-contract', - version='1.0.0', + name="openimis-be-contract", + version="1.0.0", packages=find_packages(), include_package_data=True, - license='GNU AGPL v3', - description='The openIMIS Backend Contract reference module.', + license="GNU AGPL v3", + description="The openIMIS Backend Contract reference module.", long_description=README, - long_description_content_type='text/markdown', - url='https://openimis.org/', - author='Damian Borowiecki', - author_email='dborowiecki@soldevelo.com', + long_description_content_type="text/markdown", + url="https://openimis.org/", + author="Damian Borowiecki", + author_email="dborowiecki@soldevelo.com", install_requires=[ - 'django', - 'django-db-signals', - 'djangorestframework', - 'openimis-be-core', - 'openimis-be-policyholder', - 'openimis-be-contribution_plan', - 'openimis-be-payment', - 'openimis-be-contribution', - 'openimis-be-insuree', - 'openimis-be-policy', - 'openimis-be-calculation', + "django", + "django-db-signals", + "djangorestframework", + "openimis-be-core", + "openimis-be-policyholder", + "openimis-be-contribution_plan", + "openimis-be-payment", + "openimis-be-contribution", + "openimis-be-insuree", + "openimis-be-policy", + "openimis-be-calculation", ], classifiers=[ - 'Environment :: Web Environment', - 'Framework :: Django', - 'Framework :: Django :: 2.1', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: GNU Affero General Public License v3', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', + "Environment :: Web Environment", + "Framework :: Django", + "Framework :: Django :: 2.1", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU Affero General Public License v3", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", ], )