Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CM-27: BE - Add beneficiaries #2

Merged
merged 51 commits into from
May 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
53c1ace
CM-25: initial module structure
jdolkowski May 17, 2023
830f529
CM-25: add social_protection app config
jdolkowski May 18, 2023
c62cb26
CM-25: create BenefitPlan model
jdolkowski May 18, 2023
8e423c6
CM-25: add gql_mutations
jdolkowski May 18, 2023
11b188f
CM-25: add mising fields to CreateBenefitPlanInputType
jdolkowski May 18, 2023
bcf03de
CM-25: change code length
jdolkowski May 18, 2023
b133294
CM-25: create BenefitPlanGQLType
jdolkowski May 18, 2023
7c74c05
CM-25: add Query and Mutation in schema.py
jdolkowski May 18, 2023
aeeedb7
CM-25: refactor schema.py
jdolkowski May 18, 2023
70cca87
CM-25: add benefit plan validations
jdolkowski May 18, 2023
5f20a6c
CM-25: add services.py
jdolkowski May 18, 2023
0ef3f07
CM-25: create initial test
jdolkowski May 18, 2023
fb72ce9
CM-25: fix typo
jdolkowski May 18, 2023
5a48b3c
CM-25: fix filter_fileds
jdolkowski May 18, 2023
63a9235
CM-25: add missing mutations
jdolkowski May 18, 2023
59ebb59
CM-25 add migrations
jdolkowski May 18, 2023
0c2921a
CM-25: setup tests
jdolkowski May 18, 2023
74262c5
CM-25: update module modules dependencies
jdolkowski May 18, 2023
ea40dae
CM-25 update readme
jdolkowski May 18, 2023
cd1cdd7
CM-25: add benfit plan services
jdolkowski May 18, 2023
33f0283
CM-25: route gql mutations through service
jdolkowski May 18, 2023
8fcf4cc
CM-25: validate uniqueness of code and name
jdolkowski May 18, 2023
76e86c9
CM-25: update readme
jdolkowski May 18, 2023
f7b8b48
CM-25: change get to filter
jdolkowski May 18, 2023
04862c8
CM-25: remove model meta
jdolkowski May 19, 2023
d515128
CM-25: change organization to holder
jdolkowski May 19, 2023
afdb464
CM-25: refactor filters in resolve_benefit_plan
jdolkowski May 19, 2023
aab421b
CM-25: move code and name validation to validation classes
jdolkowski May 19, 2023
f1a1b88
CM-25: create gql query to validate uniqueness of BF name and code
jdolkowski May 19, 2023
ad608fe
CM-25: class refactor
jdolkowski May 19, 2023
3328a8c
CM-25: qgl_mutations refactor
jdolkowski May 19, 2023
7663f15
CM-25: remove date_from and date_to from BenefitPlan
jdolkowski May 19, 2023
d9f23b1
CM-25: add unique name and code test cases
jdolkowski May 19, 2023
9af7372
CM-25: change filed naming convetion to autogenerated in BenefitPlan
jdolkowski May 19, 2023
cb0a3c0
CM-25: validate beneficiary_data_schema
jdolkowski May 22, 2023
9ac4357
CM-25: remove beneficiary_data_schema from filtered fields
jdolkowski May 22, 2023
232564e
CM-27: added GQL query for Beneficiary
sniedzielski May 22, 2023
f36c02c
CM-27: added GQL mutations
sniedzielski May 22, 2023
3f676ea
CM-27: added tests for beneficiary service
sniedzielski May 22, 2023
939d198
CM-27: merged from develop
sniedzielski May 22, 2023
8d2312b
CM-27: fixing one unresolved conflict, removed redundant print
sniedzielski May 22, 2023
586ba62
CM-27: minor fix in service
sniedzielski May 22, 2023
3dd146d
CM-27: fixed typo in test of services
sniedzielski May 22, 2023
9a77203
CM-27: fixed tests naming and creation of test data
sniedzielski May 22, 2023
0646bda
CM-27: fixed helper test to take benefit plan data
sniedzielski May 22, 2023
2589429
CM-27: fixes and adjustments in data helpers in tests
sniedzielski May 22, 2023
80b9be0
CM-27: added missing initialization of tests files
sniedzielski May 22, 2023
20fdb5e
CM-27: fix tests
jdolkowski May 23, 2023
2b007ae
CM-27: make validate_json_schema validate only if schema in mutation
jdolkowski May 24, 2023
47a5445
CM-27: fix name validation graphql
jdolkowski May 24, 2023
82657eb
CM-27: return mutation errors
jdolkowski May 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions social_protection/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
"gql_benefit_plan_create_perms": ["160002"],
"gql_benefit_plan_update_perms": ["160003"],
"gql_benefit_plan_delete_perms": ["160004"],
"gql_beneficiary_search_perms": ["170001"],
"gql_beneficiary_create_perms": ["170002"],
"gql_beneficiary_update_perms": ["170003"],
"gql_beneficiary_delete_perms": ["170004"],
}


Expand All @@ -16,6 +20,10 @@ class SocialProtectionConfig(AppConfig):
gql_benefit_plan_create_perms = None
gql_benefit_plan_update_perms = None
gql_benefit_plan_delete_perms = None
gql_beneficiary_search_perms = None
gql_beneficiary_create_perms = None
gql_beneficiary_update_perms = None
gql_beneficiary_delete_perms = None

def ready(self):
from core.models import ModuleConfiguration
Expand Down
117 changes: 113 additions & 4 deletions social_protection/gql_mutations.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@
BaseHistoryModelUpdateMutationMixin, BaseHistoryModelDeleteMutationMixin
from core.schema import OpenIMISMutation
from social_protection.apps import SocialProtectionConfig
from social_protection.models import BenefitPlan
from social_protection.services import BenefitPlanService
from social_protection.models import (
BenefitPlan,
Beneficiary
)
from social_protection.services import (
BenefitPlanService,
BeneficiaryService
)


class CreateBenefitPlanInputType(OpenIMISMutation.Input):
Expand All @@ -28,6 +34,20 @@ class UpdateBenefitPlanInputType(CreateBenefitPlanInputType):
id = graphene.UUID(required=True)


class CreateBeneficiaryInputType(OpenIMISMutation.Input):
status = graphene.String(required=True)
individual_id = graphene.UUID(required=False)
benefit_plan_id = graphene.UUID(required=False)

date_valid_from = graphene.Date(required=False)
date_valid_to = graphene.Date(required=False)
json_ext = graphene.types.json.JSONString(required=False)


class UpdateBeneficiaryInputType(CreateBeneficiaryInputType):
id = graphene.UUID(required=True)


class CreateBenefitPlanMutation(BaseHistoryModelCreateMutationMixin, BaseMutation):
_mutation_class = "CreateBenefitPlanMutation"
_mutation_module = "social_protection"
Expand All @@ -47,7 +67,10 @@ def _mutate(cls, user, **data):
data.pop('client_mutation_label')

service = BenefitPlanService(user)
service.create(data)
res = service.create(data)
if not res['success']:
return res
return None

class Input(CreateBenefitPlanInputType):
pass
Expand Down Expand Up @@ -75,7 +98,10 @@ def _mutate(cls, user, **data):
data.pop('client_mutation_label')

service = BenefitPlanService(user)
service.update(data)
res = service.update(data)
if not res['success']:
return res
return None

class Input(UpdateBenefitPlanInputType):
pass
Expand Down Expand Up @@ -109,3 +135,86 @@ def _mutate(cls, user, **data):

class Input(OpenIMISMutation.Input):
ids = graphene.List(graphene.UUID)


class CreateBeneficiaryMutation(BaseHistoryModelCreateMutationMixin, BaseMutation):
_mutation_class = "CreateBeneficiaryMutation"
_mutation_module = "social_protection"
_model = Beneficiary

@classmethod
def _validate_mutation(cls, user, **data):
if type(user) is AnonymousUser or not user.has_perms(
SocialProtectionConfig.gql_beneficiary_create_perms):
raise ValidationError("mutation.authentication_required")

@classmethod
def _mutate(cls, user, **data):
if "client_mutation_id" in data:
data.pop('client_mutation_id')
if "client_mutation_label" in data:
data.pop('client_mutation_label')

service = BeneficiaryService(user)
service.create(data)

class Input(CreateBeneficiaryInputType):
pass


class UpdateBeneficiaryMutation(BaseHistoryModelUpdateMutationMixin, BaseMutation):
_mutation_class = "UpdateBeneficiaryMutation"
_mutation_module = "social_protection"
_model = Beneficiary

@classmethod
def _validate_mutation(cls, user, **data):
super()._validate_mutation(user, **data)
if type(user) is AnonymousUser or not user.has_perms(
SocialProtectionConfig.gql_beneficiary_update_perms):
raise ValidationError("mutation.authentication_required")

@classmethod
def _mutate(cls, user, **data):
if "date_valid_to" not in data:
data['date_valid_to'] = None
if "client_mutation_id" in data:
data.pop('client_mutation_id')
if "client_mutation_label" in data:
data.pop('client_mutation_label')

service = BeneficiaryService(user)
service.update(data)

class Input(UpdateBeneficiaryInputType):
pass


class DeleteBeneficiaryMutation(BaseHistoryModelDeleteMutationMixin, BaseMutation):
_mutation_class = "DeleteBeneficiaryMutation"
_mutation_module = "social_protection"
_model = Beneficiary

@classmethod
def _validate_mutation(cls, user, **data):
if type(user) is AnonymousUser or not user.id or not user.has_perms(
SocialProtectionConfig.gql_beneficiary_delete_perms):
raise ValidationError("mutation.authentication_required")

@classmethod
def _mutate(cls, user, **data):
if "client_mutation_id" in data:
data.pop('client_mutation_id')
if "client_mutation_label" in data:
data.pop('client_mutation_label')

service = BeneficiaryService(user)

ids = data.get('ids')
if ids:
with transaction.atomic():
for id in ids:
service.delete({'id': id})

class Input(OpenIMISMutation.Input):
ids = graphene.List(graphene.UUID)
24 changes: 23 additions & 1 deletion social_protection/gql_queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

from core import prefix_filterset, ExtendedConnection
from policyholder.gql import PolicyHolderGQLType
from social_protection.models import BenefitPlan
from individual.gql_queries import IndividualGQLType
from social_protection.models import Beneficiary, BenefitPlan


class BenefitPlanGQLType(DjangoObjectType):
Expand All @@ -27,3 +28,24 @@ class Meta:
"version": ["exact"],
}
connection_class = ExtendedConnection


class BeneficiaryGQLType(DjangoObjectType):
uuid = graphene.String(source='uuid')

class Meta:
model = Beneficiary
interfaces = (graphene.relay.Node,)
filter_fields = {
"id": ["exact"],
"status": ["exact", "iexact", "startswith", "istartswith", "contains", "icontains"],
"date_valid_from": ["exact", "lt", "lte", "gt", "gte"],
"date_valid_to": ["exact", "lt", "lte", "gt", "gte"],
**prefix_filterset("individual__", IndividualGQLType._meta.filter_fields),
**prefix_filterset("benefit_plan__", BenefitPlanGQLType._meta.filter_fields),
"date_created": ["exact", "lt", "lte", "gt", "gte"],
"date_updated": ["exact", "lt", "lte", "gt", "gte"],
"is_deleted": ["exact"],
"version": ["exact"],
}
connection_class = ExtendedConnection
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Generated by Django 3.2.19 on 2023-05-22 12:44

import core.fields
import datetime
import dirtyfields.dirtyfields
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import simple_history.models


class Migration(migrations.Migration):

dependencies = [
('individual', '0002_add_individual_rigts_for_admin'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('social_protection', '0002_add_benefit_plan_rights_to_admin'),
]

operations = [
migrations.CreateModel(
name='HistoricalBeneficiary',
fields=[
('id', models.UUIDField(db_column='UUID', db_index=True, default=None, editable=False)),
('is_deleted', models.BooleanField(db_column='isDeleted', default=False)),
('json_ext', models.JSONField(blank=True, db_column='Json_ext', null=True)),
('date_created', core.fields.DateTimeField(db_column='DateCreated', null=True)),
('date_updated', core.fields.DateTimeField(db_column='DateUpdated', null=True)),
('version', models.IntegerField(default=1)),
('date_valid_from', core.fields.DateTimeField(db_column='DateValidFrom', default=datetime.datetime.now)),
('date_valid_to', core.fields.DateTimeField(blank=True, db_column='DateValidTo', null=True)),
('replacement_uuid', models.UUIDField(db_column='ReplacementUUID', null=True)),
('status', models.CharField(max_length=100)),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('benefit_plan', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='social_protection.benefitplan')),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('individual', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='individual.individual')),
('user_created', models.ForeignKey(blank=True, db_column='UserCreatedUUID', db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL)),
('user_updated', models.ForeignKey(blank=True, db_column='UserUpdatedUUID', db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'historical beneficiary',
'verbose_name_plural': 'historical beneficiarys',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='Beneficiary',
fields=[
('id', models.UUIDField(db_column='UUID', default=None, editable=False, primary_key=True, serialize=False)),
('is_deleted', models.BooleanField(db_column='isDeleted', default=False)),
('json_ext', models.JSONField(blank=True, db_column='Json_ext', null=True)),
('date_created', core.fields.DateTimeField(db_column='DateCreated', null=True)),
('date_updated', core.fields.DateTimeField(db_column='DateUpdated', null=True)),
('version', models.IntegerField(default=1)),
('date_valid_from', core.fields.DateTimeField(db_column='DateValidFrom', default=datetime.datetime.now)),
('date_valid_to', core.fields.DateTimeField(blank=True, db_column='DateValidTo', null=True)),
('replacement_uuid', models.UUIDField(db_column='ReplacementUUID', null=True)),
('status', models.CharField(max_length=100)),
('benefit_plan', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='social_protection.benefitplan')),
('individual', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='individual.individual')),
('user_created', models.ForeignKey(db_column='UserCreatedUUID', on_delete=django.db.models.deletion.DO_NOTHING, related_name='beneficiary_user_created', to=settings.AUTH_USER_MODEL)),
('user_updated', models.ForeignKey(db_column='UserUpdatedUUID', on_delete=django.db.models.deletion.DO_NOTHING, related_name='beneficiary_user_updated', to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
bases=(dirtyfields.dirtyfields.DirtyFieldsMixin, models.Model),
),
]
7 changes: 7 additions & 0 deletions social_protection/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.db import models
from core import models as core_models
from individual.models import Individual
from policyholder.models import PolicyHolder


Expand All @@ -14,3 +15,9 @@ class BenefitPlan(core_models.HistoryBusinessModel):
)
holder = models.ForeignKey(PolicyHolder, models.DO_NOTHING, blank=True, null=True)
beneficiary_data_schema = models.JSONField(null=True, blank=True)


class Beneficiary(core_models.HistoryBusinessModel):
individual = models.ForeignKey(Individual, models.DO_NOTHING, null=False)
benefit_plan = models.ForeignKey(BenefitPlan, models.DO_NOTHING, null=False)
status = models.CharField(max_length=100, null=False)
58 changes: 49 additions & 9 deletions social_protection/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,22 @@
from core.schema import OrderedDjangoFilterConnectionField
from core.utils import append_validity_filter
from social_protection.apps import SocialProtectionConfig
from social_protection.gql_mutations import CreateBenefitPlanMutation, UpdateBenefitPlanMutation, \
DeleteBenefitPlanMutation
from social_protection.gql_queries import BenefitPlanGQLType
from social_protection.models import BenefitPlan
from social_protection.gql_mutations import (
CreateBenefitPlanMutation,
UpdateBenefitPlanMutation,
DeleteBenefitPlanMutation,
CreateBeneficiaryMutation,
UpdateBeneficiaryMutation,
DeleteBeneficiaryMutation
)
from social_protection.gql_queries import (
BenefitPlanGQLType,
BeneficiaryGQLType
)
from social_protection.models import (
BenefitPlan,
Beneficiary
)
from social_protection.validation import validate_bf_unique_code, validate_bf_unique_name
import graphene_django_optimizer as gql_optimizer

Expand All @@ -25,6 +37,14 @@ class Query:
applyDefaultValidityFilter=graphene.Boolean(),
client_mutation_id=graphene.String()
)
beneficiary = OrderedDjangoFilterConnectionField(
BeneficiaryGQLType,
orderBy=graphene.List(of_type=graphene.String),
dateValidFrom__Gte=graphene.DateTime(),
dateValidTo__Lte=graphene.DateTime(),
applyDefaultValidityFilter=graphene.Boolean(),
client_mutation_id=graphene.String()
)
bf_code_validity = graphene.Field(
ValidationMessageGQLType,
bf_code=graphene.String(required=True),
Expand All @@ -48,7 +68,7 @@ def resolve_bf_code_validity(self, info, **kwargs):
def resolve_bf_name_validity(self, info, **kwargs):
if not info.context.user.has_perms(SocialProtectionConfig.gql_benefit_plan_search_perms):
raise PermissionDenied(_("unauthorized"))
errors = validate_bf_unique_name(kwargs['bf_code'])
errors = validate_bf_unique_name(kwargs['bf_name'])
if errors:
return ValidationMessageGQLType(False, error_message=errors[0]['message'])
else:
Expand All @@ -61,18 +81,38 @@ def resolve_benefit_plan(self, info, **kwargs):
if client_mutation_id:
filters.append(Q(mutations__mutation__client_mutation_id=client_mutation_id))

Query._check_permissions(info.context.user)
Query._check_permissions(
info.context.user,
SocialProtectionConfig.gql_benefit_plan_search_perms
)
query = BenefitPlan.objects.filter(*filters)
return gql_optimizer.query(query, info)

def resolve_beneficiary(self, info, **kwargs):
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))

Query._check_permissions(
info.context.user,
SocialProtectionConfig.gql_beneficiary_search_perms
)
query = Beneficiary.objects.filter(*filters)
return gql_optimizer.query(query, info)

@staticmethod
def _check_permissions(user):
if type(user) is AnonymousUser or not user.id or not user.has_perms(
SocialProtectionConfig.gql_benefit_plan_search_perms):
def _check_permissions(user, permission):
if type(user) is AnonymousUser or not user.id or not user.has_perms(permission):
raise PermissionError("Unauthorized")


class Mutation(graphene.ObjectType):
create_benefit_plan = CreateBenefitPlanMutation.Field()
update_benefit_plan = UpdateBenefitPlanMutation.Field()
delete_benefit_plan = DeleteBenefitPlanMutation.Field()

create_beneficiary = CreateBeneficiaryMutation.Field()
update_beneficiary = UpdateBeneficiaryMutation.Field()
delete_beneficiary = DeleteBeneficiaryMutation.Field()