From e3ddffc47c67a74dc40821d37c1b6501dd4342f6 Mon Sep 17 00:00:00 2001 From: Christophe Philemotte Date: Thu, 31 Aug 2023 09:23:28 +0200 Subject: [PATCH 1/3] List and delete policy renewals with gql or when renewing policy Policy renewals were directly exposed by the C# REST API. The policy mobile app needs to list, delete, and add policy renewals. The GraphQL API didn't allow to do so. With the present change, we can now list and delete policy renewals with a GrapQL query and mutation (resp.). As there is already a mutation to renew a given policy, we haven't added a dedicated GraphQL mutation to add a policy renewal independently. As there is a background task scheduling policy renewals, we are now soft deleting the renewals of a given policy when we manually renew it. --- policy/gql_mutations.py | 39 ++++++++++++++++++- policy/gql_queries.py | 9 ++++- .../migrations/0008_policyrenewalmutation.py | 30 ++++++++++++++ policy/models.py | 10 +++++ policy/schema.py | 23 ++++++++++- policy/services.py | 21 ++++++++++ 6 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 policy/migrations/0008_policyrenewalmutation.py diff --git a/policy/gql_mutations.py b/policy/gql_mutations.py index e0960dd..795d789 100644 --- a/policy/gql_mutations.py +++ b/policy/gql_mutations.py @@ -3,11 +3,11 @@ import graphene from django.db import transaction -from policy.services import PolicyService +from policy.services import PolicyService, PolicyRenewalService from .apps import PolicyConfig from core.schema import OpenIMISMutation -from .models import Policy, PolicyMutation +from .models import Policy, PolicyMutation, PolicyRenewal from django.contrib.auth.models import AnonymousUser from django.core.exceptions import ValidationError, PermissionDenied from django.utils.translation import gettext as _ @@ -46,6 +46,8 @@ def do_mutate(cls, perms, user, **data): from core.utils import TimeUtils data['validity_from'] = TimeUtils.now() policy = PolicyService(user).update_or_create(data, user) + policy_renewals = PolicyRenewal.objects.filter(policy=policy, validity_to__isnull=True) + [PolicyRenewalService(user).delete(policy_renewal) for policy_renewal in policy_renewals] PolicyMutation.object_mutated(user, client_mutation_id=client_mutation_id, policy=policy) return None @@ -151,6 +153,39 @@ def async_mutate(cls, user, **data): 'exc': exc}] +class DeletePolicyRenewalsMutation(OpenIMISMutation): + _mutation_module = "policy" + _mutation_class = "DeletePolicyRenewalsMutation" + + class Input(OpenIMISMutation.Input): + uuids = graphene.List(graphene.String) + + @classmethod + def async_mutate(cls, user, **data): + try: + with transaction.atomic(): + errors = [] + for policy_renewal_uuid in data["uuids"]: + policy_renewal = PolicyRenewal.objects \ + .filter(uuid=policy_renewal_uuid) \ + .first() + if policy_renewal is None: + errors += { + 'title': policy_renewal_uuid, + 'list': [{'message': _( + "policy_renewal.validation.id_does_not_exist") % {'id': policy_renewal_uuid}}] + } + continue + errors += PolicyRenewalService(user).delete(policy_renewal) + if len(errors) == 1: + errors = errors[0]['list'] + return errors + except Exception as exc: + return [{ + 'message': _("policy_renewal.mutation.failed_to_delete_policies"), + 'detail': str(exc), + 'exc': exc}] + class DeletePoliciesMutation(OpenIMISMutation): _mutation_module = "policy" _mutation_class = "DeletePoliciesMutation" diff --git a/policy/gql_queries.py b/policy/gql_queries.py index a45b483..026c397 100644 --- a/policy/gql_queries.py +++ b/policy/gql_queries.py @@ -2,7 +2,7 @@ from graphene_django import DjangoObjectType from django.utils.translation import gettext as _ from .apps import PolicyConfig -from .models import Policy +from .models import Policy, PolicyRenewal from core import prefix_filterset, filter_validity, ExtendedConnection, ExtendedRelayConnection from core.schema import OfficerGQLType from product.schema import ProductGQLType @@ -44,6 +44,13 @@ class Meta: } connection_class = ExtendedConnection +class PolicyRenewalGQLType(DjangoObjectType): + class Meta: + model = PolicyRenewal + interfaces = (graphene.relay.Node,) + filter_fields = { + } + connection_class = ExtendedConnection class PolicyAndWarningsGQLType(graphene.ObjectType): policy = graphene.Field(PolicyGQLType) diff --git a/policy/migrations/0008_policyrenewalmutation.py b/policy/migrations/0008_policyrenewalmutation.py new file mode 100644 index 0000000..d25448b --- /dev/null +++ b/policy/migrations/0008_policyrenewalmutation.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2.19 on 2023-08-30 16:33 + +import core.models +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0023_alter_jsonext_column_in_tblOfficer'), + ('policy', '0007_fix_policy_mutation_name'), + ] + + operations = [ + migrations.CreateModel( + name='PolicyRenewalMutation', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('mutation', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='policy_renewals', to='core.mutationlog')), + ('policy_renewal', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='mutations', to='policy.policyrenewal')), + ], + options={ + 'db_table': 'policy_renewal_PolicyMutation', + 'managed': True, + }, + bases=(models.Model, core.models.ObjectMutation), + ), + ] diff --git a/policy/models.py b/policy/models.py index a7de203..5b9bb45 100644 --- a/policy/models.py +++ b/policy/models.py @@ -119,3 +119,13 @@ class PolicyMutation(core_models.UUIDModel, core_models.ObjectMutation): class Meta: managed = True db_table = "policy_PolicyMutation" + +class PolicyRenewalMutation(core_models.UUIDModel, core_models.ObjectMutation): + policy_renewal = models.ForeignKey(PolicyRenewal, models.DO_NOTHING, + related_name='mutations') + mutation = models.ForeignKey( + core_models.MutationLog, models.DO_NOTHING, related_name='policy_renewals') + + class Meta: + managed = True + db_table = "policy_renewal_PolicyMutation" diff --git a/policy/schema.py b/policy/schema.py index c753c6e..fb50bf6 100644 --- a/policy/schema.py +++ b/policy/schema.py @@ -15,7 +15,7 @@ import graphene_django_optimizer as gql_optimizer from graphene_django.filter import DjangoFilterConnectionField from core.models import Officer -from .models import PolicyMutation +from .models import PolicyMutation, PolicyRenewalMutation from product.models import Product from contribution.models import Premium from insuree.models import Family, Insuree, InsureePolicy @@ -48,6 +48,7 @@ class Query(graphene.ObjectType): showInactive=graphene.Boolean(), orderBy=graphene.List(of_type=graphene.String), ) + # Note: # A Policy is bound to a Family... # but an insuree of the family is only covered by the family policy @@ -91,6 +92,11 @@ class Query(graphene.ObjectType): region=graphene.String(), ) + policy_renewals = DjangoFilterConnectionField(PolicyRenewalGQLType) + + def resolve_policy_renewals(self, info, **kwargs): + return PolicyRenewal.objects.filter(validity_to__isnull=True) + def resolve_policy_values(self, info, **kwargs): if not info.context.user.has_perms(PolicyConfig.gql_query_policies_perms): raise PermissionDenied(_("unauthorized")) @@ -319,6 +325,7 @@ class Mutation(graphene.ObjectType): create_policy = CreatePolicyMutation.Field() update_policy = UpdatePolicyMutation.Field() delete_policies = DeletePoliciesMutation.Field() + delete_policy_renewals = DeletePolicyRenewalsMutation.Field() renew_policy = RenewPolicyMutation.Field() suspend_policies = SuspendPoliciesMutation.Field() @@ -336,6 +343,20 @@ def on_policy_mutation(sender, **kwargs): policy=policy, mutation_id=kwargs['mutation_log_id']) return [] +def on_policy_renewal_mutation(sender, **kwargs): + uuids = kwargs['data'].get('uuids', []) + if not uuids: + uuid = kwargs['data'].get('policy_renewal_uuid', None) + uuids = [uuid] if uuid else [] + if not uuids: + return [] + impacted_policy_renewals = PolicyRenewal.objects.filter(uuid__in=uuids).all() + for policy_renewal in impacted_policy_renewals: + PolicyRenewalMutation.objects.create( + policy_renewal=policy_renewal, mutation_id=kwargs['mutation_log_id']) + return [] + def bind_signals(): signal_mutation_module_validate["policy"].connect(on_policy_mutation) + signal_mutation_module_validate["policy"].connect(on_policy_renewal_mutation) diff --git a/policy/services.py b/policy/services.py index 3e4ac75..d24069c 100644 --- a/policy/services.py +++ b/policy/services.py @@ -35,6 +35,27 @@ def reset_policy_before_update(policy): policy.officer_id = None +class PolicyRenewalService: + def __init__(self, user): + self.user = user + + def delete(self, policy_renewal): + try: + policy_renewal = PolicyRenewal.objects.get(uuid=policy_renewal.uuid) + logger.debug(f'YEAHHHH {policy_renewal.uuid}') + # policy_renewal.details.all().delete() + policy_renewal.delete_history() + return [] + except Exception as exc: + logger.error(f'ERROR {exc}') + return { + 'title': policy_renewal.uuid, + 'list': [{ + 'message': _("policy_renewal.mutation.failed_to_delete_policy_renewal") % {'policy_renewal': str(policy_renewal)}, + 'detail': policy_renewal.uuid}] + } + + class PolicyService: def __init__(self, user): self.user = user From ac3ae2e92049b067fcf49e46506b2698bfa721a2 Mon Sep 17 00:00:00 2001 From: Thibault Dethier Date: Wed, 7 Feb 2024 18:52:06 +0100 Subject: [PATCH 2/3] Handling policy renewals through mobile app --- policy/gql_mutations.py | 49 ++++++++++++----------------------------- policy/schema.py | 25 +++++++-------------- policy/services.py | 6 ++--- 3 files changed, 25 insertions(+), 55 deletions(-) diff --git a/policy/gql_mutations.py b/policy/gql_mutations.py index 795d789..ff4d3ce 100644 --- a/policy/gql_mutations.py +++ b/policy/gql_mutations.py @@ -46,8 +46,20 @@ def do_mutate(cls, perms, user, **data): from core.utils import TimeUtils data['validity_from'] = TimeUtils.now() policy = PolicyService(user).update_or_create(data, user) - policy_renewals = PolicyRenewal.objects.filter(policy=policy, validity_to__isnull=True) - [PolicyRenewalService(user).delete(policy_renewal) for policy_renewal in policy_renewals] + logger.info(f"After policy create_or_update: {policy.uuid}") + if data["stage"] == Policy.STAGE_RENEWED: + logger.info("Deleting the optional PolicyRenewals after renewing") + previous_policy = (Policy.objects.filter(validity_to__isnull=True, + family_id=data["family_id"], + product_id=data["product_id"], + status__in=[Policy.STATUS_EXPIRED, Policy.STATUS_ACTIVE]) + .order_by("-id") + .first()) + if not previous_policy: + logger.error("Can't find the policy that was renewed - not deleting the PolicyRenewals") + policy_renewals = PolicyRenewal.objects.filter(policy=previous_policy, validity_to__isnull=True) + logger.info(f"Total PolicyRenewals found: {policy_renewals.count()}") + [PolicyRenewalService(user).delete(policy_renewal) for policy_renewal in policy_renewals] PolicyMutation.object_mutated(user, client_mutation_id=client_mutation_id, policy=policy) return None @@ -153,39 +165,6 @@ def async_mutate(cls, user, **data): 'exc': exc}] -class DeletePolicyRenewalsMutation(OpenIMISMutation): - _mutation_module = "policy" - _mutation_class = "DeletePolicyRenewalsMutation" - - class Input(OpenIMISMutation.Input): - uuids = graphene.List(graphene.String) - - @classmethod - def async_mutate(cls, user, **data): - try: - with transaction.atomic(): - errors = [] - for policy_renewal_uuid in data["uuids"]: - policy_renewal = PolicyRenewal.objects \ - .filter(uuid=policy_renewal_uuid) \ - .first() - if policy_renewal is None: - errors += { - 'title': policy_renewal_uuid, - 'list': [{'message': _( - "policy_renewal.validation.id_does_not_exist") % {'id': policy_renewal_uuid}}] - } - continue - errors += PolicyRenewalService(user).delete(policy_renewal) - if len(errors) == 1: - errors = errors[0]['list'] - return errors - except Exception as exc: - return [{ - 'message': _("policy_renewal.mutation.failed_to_delete_policies"), - 'detail': str(exc), - 'exc': exc}] - class DeletePoliciesMutation(OpenIMISMutation): _mutation_module = "policy" _mutation_class = "DeletePoliciesMutation" diff --git a/policy/schema.py b/policy/schema.py index fb50bf6..348fbc3 100644 --- a/policy/schema.py +++ b/policy/schema.py @@ -15,7 +15,6 @@ import graphene_django_optimizer as gql_optimizer from graphene_django.filter import DjangoFilterConnectionField from core.models import Officer -from .models import PolicyMutation, PolicyRenewalMutation from product.models import Product from contribution.models import Premium from insuree.models import Family, Insuree, InsureePolicy @@ -95,7 +94,14 @@ class Query(graphene.ObjectType): policy_renewals = DjangoFilterConnectionField(PolicyRenewalGQLType) def resolve_policy_renewals(self, info, **kwargs): - return PolicyRenewal.objects.filter(validity_to__isnull=True) + if not info.context.user.has_perms(PolicyConfig.gql_mutation_renew_policies_perms): + raise PermissionDenied(_("unauthorized")) + user = info.context.user + filters = Q(validity_to__isnull=True) + if hasattr(user, "is_imis_admin") and not user.is_imis_admin: + enrollment_officer = user.officer + filters &= Q(new_officer=enrollment_officer) + return PolicyRenewal.objects.filter(filters) def resolve_policy_values(self, info, **kwargs): if not info.context.user.has_perms(PolicyConfig.gql_query_policies_perms): @@ -325,7 +331,6 @@ class Mutation(graphene.ObjectType): create_policy = CreatePolicyMutation.Field() update_policy = UpdatePolicyMutation.Field() delete_policies = DeletePoliciesMutation.Field() - delete_policy_renewals = DeletePolicyRenewalsMutation.Field() renew_policy = RenewPolicyMutation.Field() suspend_policies = SuspendPoliciesMutation.Field() @@ -343,20 +348,6 @@ def on_policy_mutation(sender, **kwargs): policy=policy, mutation_id=kwargs['mutation_log_id']) return [] -def on_policy_renewal_mutation(sender, **kwargs): - uuids = kwargs['data'].get('uuids', []) - if not uuids: - uuid = kwargs['data'].get('policy_renewal_uuid', None) - uuids = [uuid] if uuid else [] - if not uuids: - return [] - impacted_policy_renewals = PolicyRenewal.objects.filter(uuid__in=uuids).all() - for policy_renewal in impacted_policy_renewals: - PolicyRenewalMutation.objects.create( - policy_renewal=policy_renewal, mutation_id=kwargs['mutation_log_id']) - return [] - def bind_signals(): signal_mutation_module_validate["policy"].connect(on_policy_mutation) - signal_mutation_module_validate["policy"].connect(on_policy_renewal_mutation) diff --git a/policy/services.py b/policy/services.py index d24069c..c676788 100644 --- a/policy/services.py +++ b/policy/services.py @@ -41,10 +41,10 @@ def __init__(self, user): def delete(self, policy_renewal): try: - policy_renewal = PolicyRenewal.objects.get(uuid=policy_renewal.uuid) - logger.debug(f'YEAHHHH {policy_renewal.uuid}') - # policy_renewal.details.all().delete() policy_renewal.delete_history() + logger.info(f"Deleting the related policy renewal details, if any") + for detail in policy_renewal.details.all(): + detail.delete_history() return [] except Exception as exc: logger.error(f'ERROR {exc}') From 8825a074d64ae03603ff5529cf7d9778720368fb Mon Sep 17 00:00:00 2001 From: Kamil Malinowski Date: Thu, 12 Sep 2024 14:49:57 +0200 Subject: [PATCH 3/3] Merged Migrations --- policy/migrations/0010_merge_20240912_1248.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 policy/migrations/0010_merge_20240912_1248.py diff --git a/policy/migrations/0010_merge_20240912_1248.py b/policy/migrations/0010_merge_20240912_1248.py new file mode 100644 index 0000000..8b5da6b --- /dev/null +++ b/policy/migrations/0010_merge_20240912_1248.py @@ -0,0 +1,14 @@ +# Generated by Django 3.2.25 on 2024-09-12 12:48 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('policy', '0008_policyrenewalmutation'), + ('policy', '0009_auto_20240716_1744'), + ] + + operations = [ + ]