Skip to content

Commit

Permalink
Merge pull request #107 from openimis/release/24.04
Browse files Browse the repository at this point in the history
MERGING release/24.04 into main
  • Loading branch information
delcroip authored Jun 26, 2024
2 parents 5dc83fe + c2d6109 commit fb861d6
Show file tree
Hide file tree
Showing 8 changed files with 322 additions and 73 deletions.
2 changes: 2 additions & 0 deletions policy/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"ACTIVATION_OPTION_CONTRIBUTION": 1,
"CTIVATION_OPTION_PAYMENT": 2,
"ACTIVATION_OPTION_READY": 3,
"contribution_receipt_length": 5
}


Expand All @@ -44,6 +45,7 @@ class PolicyConfig(AppConfig):
ACTIVATION_OPTION_PAYMENT = None
ACTIVATION_OPTION_READY = None
activation_option = None
contribution_receipt_length = None

def __load_config(self, cfg):
for field in cfg:
Expand Down
3 changes: 3 additions & 0 deletions policy/gql_mutations.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ class PolicyInputType(OpenIMISMutation.Input):
product_id = graphene.Int(required=True)
family_id = graphene.Int(required=True)
officer_id = graphene.Int(required=True)
is_paid = graphene.Boolean(required=False)
receipt = graphene.String(required=False)
payer_uuid = graphene.String(required=False)


class CreateRenewOrUpdatePolicyMutation(OpenIMISMutation):
Expand Down
4 changes: 3 additions & 1 deletion policy/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class Query(graphene.ObjectType):
active_or_last_expired_only=graphene.Boolean(),
show_history=graphene.Boolean(),
order_by=graphene.String(),
target_date=graphene.Date(),
)
# TODO: refactoring
# Eligibility is calculated for a Policy... which is bound to a Family (not an Insuree)
Expand Down Expand Up @@ -218,7 +219,8 @@ def resolve_policies_by_family(self, info, **kwargs):
active_or_last_expired_only=kwargs.get(
'active_or_last_expired_only', False),
show_history=kwargs.get('show_history', False),
order_by=kwargs.get('order_by', None)
order_by=kwargs.get('order_by', None),
target_date=kwargs.get('target_date', None)
)
res = ByFamilyService(user=info.context.user).request(req)
return [Query._to_policy_by_family_or_insuree_item(x) for x in res.items]
Expand Down
78 changes: 54 additions & 24 deletions policy/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import core
from claim.models import ClaimService, Claim, ClaimItem
from django import dispatch
from django.core.exceptions import PermissionDenied
from django.core.exceptions import PermissionDenied, ValidationError
from django.db import connection
from django.db.models import Q, Count, Min, Max, Sum, F
from django.db.models.functions import Coalesce
Expand Down Expand Up @@ -43,13 +43,17 @@ def __init__(self, user):
@register_service_signal('policy_service.create_or_update')
def update_or_create(self, data, user):
policy_uuid = data.get('uuid', None)
if 'enroll_date' in data and data['enroll_date'] > py_date.today():
raise ValidationError("policy.enroll_date_in_the_future")
if policy_uuid:
return self.update_policy(data, user)
else:
return self.create_policy(data, user)

@register_service_signal('policy_service.update')
def update_policy(self, data, user):
if "is_paid" in data:
data.pop("is_paid")
data = self._clean_mutation_info(data)
policy_uuid = data.pop('uuid') if 'uuid' in data else None
policy = Policy.objects.get(uuid=policy_uuid)
Expand All @@ -62,12 +66,41 @@ def update_policy(self, data, user):

@register_service_signal('policy_service.create')
def create_policy(self, data, user):
is_paid = data.pop("is_paid", False)
receipt = data.pop("receipt", None)
payer_uuid = data.pop("payer_uuid", None)
data = self._clean_mutation_info(data)
policy = Policy.objects.create(**data)
if receipt is not None:
from contribution.services import check_unique_premium_receipt_code_within_product
is_invalid = check_unique_premium_receipt_code_within_product(code=receipt, policy_uuid=policy.uuid)
if is_invalid:
raise ValidationError("Receipt already exist for a given product.")
else:
receipt = self.generate_contribution_receipt(policy.product, policy.enroll_date)
policy.save()
update_insuree_policies(policy, user.id_for_audit)
if is_paid:
from contribution.gql_mutations import premium_action
premium_data = {"policy_uuid": policy.uuid, "amount": policy.value,
"receipt": receipt, "pay_date": data["enroll_date"], "pay_type": "C"}
if payer_uuid is not None:
premium_data["payer_uuid"] = payer_uuid
premium_action(premium_data, user)
return policy

def generate_contribution_receipt(self, product, enroll_date):
from contribution.models import Premium
code_length = PolicyConfig.contribution_receipt_length
if not code_length and type(code_length) is not int:
raise ValueError("Invalid config for `generate_contribution_receipt`, expected `code_length` value.")
prefix = "RE-" + str(product.code) + "-" + str(enroll_date) + "-"
last_contribution = Premium.objects.filter(validity_to__isnull=True, receipt__icontains=prefix)
code = 0
if last_contribution:
code = int(last_contribution.latest('receipt').receipt[-code_length:])
return prefix + str(code + 1).zfill(code_length)

def _clean_mutation_info(self, data):
if "client_mutation_id" in data:
data.pop('client_mutation_id')
Expand Down Expand Up @@ -283,10 +316,14 @@ def build_query(self, req):
.annotate(total_rem_delivery=Sum('claim_ded_rems__rem_delivery')) \
.annotate(total_rem_hospitalization=Sum('claim_ded_rems__rem_hospitalization')) \
.annotate(total_rem_antenatal=Sum('claim_ded_rems__rem_antenatal'))

res.query.group_by = ['id']
if hasattr(req, 'chf_id'):
res= res.filter(insuree_policies__insuree__chf_id = req.chf_id)
if not req.show_history:
res = res.filter(*core.filter_validity())
if req.target_date:
res = res.filter(*core.filter_validity(), expiry_date__gt = req.target_date, effective_date__lte = req.target_date)
else:
res = res.filter(*core.filter_validity())
if req.active_or_last_expired_only:
# sort on status, so that any active policy (status = 2) pops up...
res = res.annotate(not_null_expiry_date=Coalesce('expiry_date', py_date.max)) \
Expand All @@ -300,25 +337,17 @@ def __init__(self, user):
super(ByInsureeService, self).__init__(user)

def request(self, by_insuree_request):
insurees = Insuree.objects.filter(
chf_id=by_insuree_request.chf_id,
*core.filter_validity() if not by_insuree_request.show_history else []
)
res = self.build_query(by_insuree_request)
res = res.prefetch_related('insuree_policies')
res = res.filter(insuree_policies__insuree__in=insurees)
# .distinct('product__code') >> DISTINCT ON fields not supported by MS-SQL
if by_insuree_request.target_date:
res = get_queryset_valid_at_date(res, by_insuree_request.target_date)
res = res.filter(insuree_policies__insuree__chf_id=by_insuree_request.chf_id)
if by_insuree_request.active_or_last_expired_only:
products = {}
for r in res:
if r.product.code not in products.keys():
products[r.product.code] = r
for policy in res:
if policy.status == Policy.STATUS_IDLE or policy.status == Policy.STATUS_READY:
products['policy.product.code-%s' % policy.uuid] = policy
elif policy.product.code not in products.keys():
products[policy.product.code] = policy
res = products.values()
items = tuple(
map(lambda x: FilteredPoliciesService._to_item(x), res)
)
items = [FilteredPoliciesService._to_item(x) for x in res]
# possible improvement: sort via the ORM
# ... but beware of the active_or_last_expired_only filtering!
order_attr = to_snake_case(by_insuree_request.order_by if by_insuree_request.order_by else "expiry_date")
Expand All @@ -336,11 +365,12 @@ def request(self, by_insuree_request):
@core.comparable
class ByFamilyRequest(object):

def __init__(self, family_uuid, active_or_last_expired_only=False, show_history=False, order_by=None):
def __init__(self, family_uuid, active_or_last_expired_only=False, show_history=False, order_by=None, target_date=None):
self.family_uuid = family_uuid
self.active_or_last_expired_only = active_or_last_expired_only
self.show_history = show_history
self.order_by = order_by
self.target_date = target_date

def __eq__(self, other):
return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
Expand All @@ -362,9 +392,8 @@ def __init__(self, user):
super(ByFamilyService, self).__init__(user)

def request(self, by_family_request):
family = Family.objects.get(uuid=by_family_request.family_uuid)
res = self.build_query(by_family_request)
res = res.filter(family_id=family.id)
res = res.filter(family__uuid=by_family_request.family_uuid)
# .distinct('product__code') >> DISTINCT ON fields not supported by MS-SQL
if by_family_request.active_or_last_expired_only:
products = {}
Expand Down Expand Up @@ -468,8 +497,8 @@ def __repr__(self):
return self.__str__()


signal_eligibility_service_before = dispatch.Signal(providing_args=["user", "request", "response"])
signal_eligibility_service_after = dispatch.Signal(providing_args=["user", "request", "response"])
signal_eligibility_service_before = dispatch.Signal(["user", "request", "response"])
signal_eligibility_service_after = dispatch.Signal(["user", "request", "response"])


class EligibilityService(object):
Expand Down Expand Up @@ -620,7 +649,7 @@ def get_eligibility(self, insuree, item_or_service, model, req, now):
if min_date_qs["min_date_lte"]
else min_date_qs["min_date_all"])

if queryset_item_or_service.filter(min_date__lte=now).filter(left__isnull=True).first():
if queryset_item_or_service.filter(min_date__lte=now).filter(left__isnull=True).order_by('-validity_from').first():
items_or_services_left = None
else:
items_or_services_left = queryset_item_or_service\
Expand Down Expand Up @@ -707,6 +736,7 @@ def get_total_filter(category):
filter=get_total_filter(Service.CATEGORY_VISIT),
distinct=True), 0)) \
.annotate(total_visits_left=F("policy__product__max_no_visits") - F("total_visits")) \
.order_by('-expiry_date')\
.first()

if result is None:
Expand Down
2 changes: 1 addition & 1 deletion policy/signals.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from core.signals import Signal

_check_formal_sector_for_policy_signal_params = ["user", "policy_id"]
signal_check_formal_sector_for_policy = Signal(providing_args=_check_formal_sector_for_policy_signal_params)
signal_check_formal_sector_for_policy = Signal(_check_formal_sector_for_policy_signal_params)
37 changes: 5 additions & 32 deletions policy/test_helpers.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from contribution.models import Premium
from insuree.models import InsureePolicy, Family, Gender, Insuree
from insuree.test_helpers import create_test_insuree
from policy.models import Policy
from policy.values import policy_values
from product.models import Product
from core.utils import filter_validity
import datetime


def create_test_policy(product, insuree, link=True, valid=True, custom_props=None, check=False):
"""
Compatibility method that only return the Policy
Expand Down Expand Up @@ -132,37 +135,7 @@ def create_test_policy_with_IPs(product, insuree, valid=True, policy_props=None,
def create_test_insuree_for_policy(with_family=True, is_head=False, custom_props=None, family_custom_props=None):
# To establish the mandatory reference between "Insuree" and "Family" objects, we can insert the "Family" object
# with a temporary ID and later update it to associate with the respective "Insuree" object.
if with_family:
family = Family.objects.create(
validity_from="2019-01-01",
head_insuree_id=1, # dummy
audit_user_id=-1,
**(family_custom_props if family_custom_props else {})
)
else:
family = None
insuree= create_test_insuree(with_family=with_family, is_head=is_head, custom_props=custom_props, family_custom_props=family_custom_props)

insuree = Insuree.objects.create(
**{
"last_name": "Test Last",
"other_names": "First Second",
"chf_id": "chf_dflt",
"family": family,
"gender": Gender.objects.get(code='M'),
"dob": dts("1970-01-01"),
"head": is_head,
"card_issued": True,
"validity_from": dts("2019-01-01"),
"audit_user_id": -1,
**(custom_props if custom_props else {})
}
)
insuree.save()
if with_family:
family.head_insuree_id = insuree.id
if family_custom_props:
for k, v in family_custom_props.items():
setattr(family, k, v)
family.save()

return insuree, family
return insuree, insuree.family
30 changes: 19 additions & 11 deletions policy/test_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from claim.test_helpers import create_test_claim, create_test_claimservice, create_test_claimitem
from claim.validations import validate_claim, validate_assign_prod_to_claimitems_and_services, process_dedrem
from core.models import InteractiveUser, User
from core.test_helpers import create_test_officer
from core.test_helpers import create_test_officer
from django.conf import settings
from django.test import TestCase
from insuree.test_helpers import create_test_photo
Expand All @@ -12,9 +12,12 @@
from insuree.test_helpers import create_test_insuree
from policy.test_helpers import create_test_policy2, create_test_insuree_for_policy
from product.test_helpers import create_test_product, create_test_product_service, create_test_product_item

from location.test_helpers import create_test_health_facility
from .services import *

from medical_pricelist.test_helpers import (
create_test_item_pricelist,
create_test_service_pricelist
)

class EligibilityServiceTestCase(TestCase):
def setUp(self) -> None:
Expand All @@ -33,6 +36,7 @@ def test_eligibility_request_permission_denied(self):
service.request(req)
mock_user.has_perms.assert_called_with(PolicyConfig.gql_query_eligibilities_perms)

@skip("this test hangs on psql, the mock destroys normal queries happening inside EligibilityRequest")
def test_eligibility_request_all_good(self):
with mock.patch("django.db.backends.utils.CursorWrapper") as mock_cursor:
return_values = [
Expand Down Expand Up @@ -329,7 +333,12 @@ def test_eligibility_stored_proc_item_no_insuree_policy(self):
insuree.delete()

def test_eligibility_signal(self):

insuree, family = create_test_insuree_for_policy()
#spl = create_test_service_pricelist(location_id=family.location.parent.id)
#ipl = create_test_item_pricelist(location_id=family.location.parent.id)
#hf =create_test_health_facility(code= 'tst-18', location_id=family.location.parent.id, custom_props={'id':18, 'items_pricelist': ipl, 'services_pricelist': spl })

product = create_test_product("ELI1")
(policy, insuree_policy) = create_test_policy2(product, insuree)
item = create_test_item("A")
Expand Down Expand Up @@ -474,12 +483,11 @@ def test_renewals_sms(self):
from core import datetime, datetimedelta

insuree, family = create_test_insuree_for_policy(
custom_props={"chf_id": "TESTCHFSMS", "phone": "+33644444719"},
family_custom_props={"location_id": 62},
)
custom_props={"chf_id": "TESTCHFSMS", 'last_name':'Test Last',"phone": "+33644444719"} )
product = create_test_product("VISIT")
officer = create_test_officer(
custom_props={"phone": "+32444444444", "phone_communication": True}
custom_props={"phone": "+32444444444", "phone_communication": True},
villages = [family.location]
)

(policy_expiring, _) = create_test_policy2(
Expand Down Expand Up @@ -517,9 +525,9 @@ def test_renewals_sms(self):
officer_sms = [sms for sms in sms_queue if sms.phone == "+32444444444"]
self.assertEquals(len(officer_sms), 1)
self.assertIn("TESTCHFSMS", officer_sms[0].sms_message)
self.assertIn("Agilo", officer_sms[0].sms_message)
self.assertIn("Remorlogy", officer_sms[0].sms_message)
self.assertIn("Jambero", officer_sms[0].sms_message)
self.assertIn(family.location.name, officer_sms[0].sms_message)
self.assertIn(family.location.parent.name, officer_sms[0].sms_message)
self.assertIn(family.location.parent.parent.name, officer_sms[0].sms_message)
self.assertIn("Test product VISIT", officer_sms[0].sms_message)

# tearDown
Expand All @@ -540,7 +548,7 @@ def test_insert_renewal_details(self):
insuree_newpic, family_newpic = create_test_insuree_for_policy(
custom_props={"photo_date": datetime.datetime.now() - datetimedelta(days=30)})
insuree_oldpic, family_oldpic = create_test_insuree_for_policy(
custom_props={"photo_date": "2010-01-01", "chf_id": "CHFMARK"}) # 5 years by default
custom_props={"photo_date": "2010-01-01", "chf_id": "CHFMARK", 'last_name':'Test Last'}) # 5 years by default
product = create_test_product("VISIT")
officer = create_test_officer(custom_props={"phone": "+32444444444", "phone_communication": True})
photo_newpic = create_test_photo(insuree_newpic.id, officer.id)
Expand Down
Loading

0 comments on commit fb861d6

Please sign in to comment.