From be6654c775b9ae8bd21c698fa5dc72e5bf1bd750 Mon Sep 17 00:00:00 2001 From: sniedzielski Date: Mon, 25 Oct 2021 14:52:15 +0200 Subject: [PATCH 01/15] OPL-26: added skeleton calculation rule for product modelling --- calcrule_contribution_legacy/__init__.py | 1 + calcrule_contribution_legacy/apps.py | 24 ++++++- .../calculation_rule.py | 64 +++++++++++++++++++ calcrule_contribution_legacy/config.py | 12 ++++ 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 calcrule_contribution_legacy/calculation_rule.py create mode 100644 calcrule_contribution_legacy/config.py diff --git a/calcrule_contribution_legacy/__init__.py b/calcrule_contribution_legacy/__init__.py index e69de29..9a689ff 100644 --- a/calcrule_contribution_legacy/__init__.py +++ b/calcrule_contribution_legacy/__init__.py @@ -0,0 +1 @@ +default_app_config = 'calcrule_contribution_legacy.apps.CalcruleContributionLegacyConfig' diff --git a/calcrule_contribution_legacy/apps.py b/calcrule_contribution_legacy/apps.py index 35198cd..67c86ef 100644 --- a/calcrule_contribution_legacy/apps.py +++ b/calcrule_contribution_legacy/apps.py @@ -1,5 +1,27 @@ +import importlib +import inspect from django.apps import AppConfig +from calculation.apps import CALCULATION_RULES + +from core.abs_calculation_rule import AbsCalculationRule + + +MODULE_NAME = "calcrule_contribution_legacy" +DEFAULT_CFG = {} + + +def read_all_calculation_rules(): + """function to read all calculation rules from that module""" + for name, cls in inspect.getmembers(importlib.import_module("calcrule_contribution_legacy.calculation_rule"), inspect.isclass): + if 'calcrule' in cls.__module__.split('.')[0]: + CALCULATION_RULES.append(cls) + cls.ready() class CalcruleContributionLegacyConfig(AppConfig): - name = 'calcrule_contribution_legacy' + name = MODULE_NAME + + def ready(self): + from core.models import ModuleConfiguration + cfg = ModuleConfiguration.get_or_default(MODULE_NAME, DEFAULT_CFG) + read_all_calculation_rules() diff --git a/calcrule_contribution_legacy/calculation_rule.py b/calcrule_contribution_legacy/calculation_rule.py new file mode 100644 index 0000000..739e0d1 --- /dev/null +++ b/calcrule_contribution_legacy/calculation_rule.py @@ -0,0 +1,64 @@ +import json + +from .apps import AbsCalculationRule +from .config import CLASS_RULE_PARAM_VALIDATION, \ + DESCRIPTION_CONTRIBUTION_VALUATION, FROM_TO +from core.signals import Signal +from core import datetime +from django.contrib.contenttypes.models import ContentType + + +class ContributionPlanCalculationRuleProductModeling(AbsCalculationRule): + version = 1 + uuid = "2aee6d54-eef4-4ee6-1c47-2793cfa5f9a8" + calculation_rule_name = "payment: fee for service" + description = DESCRIPTION_CONTRIBUTION_VALUATION + impacted_class_parameter = CLASS_RULE_PARAM_VALIDATION + date_valid_from = datetime.datetime(2000, 1, 1) + date_valid_to = None + status = "active" + from_to = FROM_TO + + signal_get_rule_name = Signal(providing_args=[]) + signal_get_rule_details = Signal(providing_args=[]) + signal_get_param = Signal(providing_args=[]) + signal_get_linked_class = Signal(providing_args=[]) + signal_calculate_event = Signal(providing_args=[]) + signal_convert_from_to = Signal(providing_args=[]) + + @classmethod + def ready(cls): + now = datetime.datetime.now() + condition_is_valid = (now >= cls.date_valid_from and now <= cls.date_valid_to) \ + if cls.date_valid_to else (now >= cls.date_valid_from and cls.date_valid_to is None) + if condition_is_valid: + if cls.status == "active": + # register signals getParameter to getParameter signal and getLinkedClass ot getLinkedClass signal + cls.signal_get_rule_name.connect(cls.get_rule_name, dispatch_uid="on_get_rule_name_signal") + cls.signal_get_rule_details.connect(cls.get_rule_details, dispatch_uid="on_get_rule_details_signal") + cls.signal_get_param.connect(cls.get_parameters, dispatch_uid="on_get_param_signal") + cls.signal_get_linked_class.connect(cls.get_linked_class, dispatch_uid="on_get_linked_class_signal") + cls.signal_calculate_event.connect(cls.run_calculation_rules, dispatch_uid="on_calculate_event_signal") + cls.signal_convert_from_to.connect(cls.run_convert, dispatch_uid="on_convert_from_to") + + @classmethod + def active_for_object(cls, instance, context, type='account_receivable', sub_type='contribution'): + return instance.__class__.__name__ == "ContributionPlan" \ + and context in ["submit"] \ + and cls.check_calculation(instance) + + @classmethod + def check_calculation(cls, instance): + pass + + @classmethod + def calculate(cls, instance, *args): + pass + + @classmethod + def get_linked_class(cls, sender, class_name, **kwargs): + pass + + @classmethod + def convert(cls, instance, convert_from, convert_to, **kwargs): + pass diff --git a/calcrule_contribution_legacy/config.py b/calcrule_contribution_legacy/config.py new file mode 100644 index 0000000..b9fd2a4 --- /dev/null +++ b/calcrule_contribution_legacy/config.py @@ -0,0 +1,12 @@ +CLASS_RULE_PARAM_VALIDATION = None + +FROM_TO = [ + {"from": "Policy", "to": "Invoice"}, + {"from": "Contract", "to": "Invoice"} +] + +DESCRIPTION_CONTRIBUTION_VALUATION = F"" \ + F"This calculation will, for the selected level and product," \ + F" calculate how much the insuree will have to" \ + F" pay based on the product modeling," \ + F" it will also manage the conversion into an invoice" From 2e899bf3ce990917b52b493c5f6c174365276c9b Mon Sep 17 00:00:00 2001 From: sniedzielski Date: Wed, 27 Oct 2021 15:33:34 +0200 Subject: [PATCH 02/15] OPL-26: added converter 'policy->invoice' --- calcrule_contribution_legacy/apps.py | 2 +- .../calculation_rule.py | 59 ++++++++++++- calcrule_contribution_legacy/config.py | 2 +- .../converters/__init__.py | 0 .../converters/policy_to_invoice.py | 79 ++++++++++++++++++ .../converters/policy_to_line_item.py | 83 +++++++++++++++++++ setup.py | 3 + 7 files changed, 223 insertions(+), 5 deletions(-) create mode 100644 calcrule_contribution_legacy/converters/__init__.py create mode 100644 calcrule_contribution_legacy/converters/policy_to_invoice.py create mode 100644 calcrule_contribution_legacy/converters/policy_to_line_item.py diff --git a/calcrule_contribution_legacy/apps.py b/calcrule_contribution_legacy/apps.py index 67c86ef..21a600d 100644 --- a/calcrule_contribution_legacy/apps.py +++ b/calcrule_contribution_legacy/apps.py @@ -13,7 +13,7 @@ def read_all_calculation_rules(): """function to read all calculation rules from that module""" for name, cls in inspect.getmembers(importlib.import_module("calcrule_contribution_legacy.calculation_rule"), inspect.isclass): - if 'calcrule' in cls.__module__.split('.')[0]: + if cls.__module__.split('.')[1] == 'calculation_rule': CALCULATION_RULES.append(cls) cls.ready() diff --git a/calcrule_contribution_legacy/calculation_rule.py b/calcrule_contribution_legacy/calculation_rule.py index 739e0d1..c3a46f0 100644 --- a/calcrule_contribution_legacy/calculation_rule.py +++ b/calcrule_contribution_legacy/calculation_rule.py @@ -3,9 +3,13 @@ from .apps import AbsCalculationRule from .config import CLASS_RULE_PARAM_VALIDATION, \ DESCRIPTION_CONTRIBUTION_VALUATION, FROM_TO +from .converters.policy_to_invoice import PolicyToInvoiceConverter from core.signals import Signal from core import datetime from django.contrib.contenttypes.models import ContentType +from django.db.models.query import Q +from policy.models import Policy +from policy.values import policy_values class ContributionPlanCalculationRuleProductModeling(AbsCalculationRule): @@ -18,6 +22,8 @@ class ContributionPlanCalculationRuleProductModeling(AbsCalculationRule): date_valid_to = None status = "active" from_to = FROM_TO + type = "account_receivable" + sub_type = "contribution" signal_get_rule_name = Signal(providing_args=[]) signal_get_rule_details = Signal(providing_args=[]) @@ -42,14 +48,32 @@ def ready(cls): cls.signal_convert_from_to.connect(cls.run_convert, dispatch_uid="on_convert_from_to") @classmethod - def active_for_object(cls, instance, context, type='account_receivable', sub_type='contribution'): + def active_for_object(cls, instance, context, type, sub_type): return instance.__class__.__name__ == "ContributionPlan" \ and context in ["submit"] \ and cls.check_calculation(instance) @classmethod def check_calculation(cls, instance): - pass + class_name = instance.__class__.__name__ + match = False + if class_name == "ContributionPlan": + match = cls.uuid == instance.calculation + elif class_name == "PolicyHolderInsuree": + match = cls.check_calculation(instance.cpb) + elif class_name == "ContractDetails": + match = cls.check_calculation(instance.cpb) + elif class_name == "ContractContributionPlanDetails": + match = cls.check_calculation(instance.cp) + elif class_name == "ContributionPlanBundle": + for cp in instance.cp: + if cls.check_calculation(cp): + match = True + break + # for legacy the calculation is valid for all famillies + elif class_name == "Family": + match = True + return match @classmethod def calculate(cls, instance, *args): @@ -57,8 +81,37 @@ def calculate(cls, instance, *args): @classmethod def get_linked_class(cls, sender, class_name, **kwargs): - pass + list_class = [] + if class_name != None: + model_class = ContentType.objects.filter(model=class_name).first() + if model_class: + model_class = model_class.model_class() + list_class = list_class + \ + [f.remote_field.model.__name__ for f in model_class._meta.fields + if f.get_internal_type() == 'ForeignKey' and f.remote_field.model.__name__ != "User"] + else: + list_class.append("Calculation") + # because we have calculation in ContributionPlan + # as uuid - we have to consider this case + if class_name == "ContributionPlan": + list_class.append("Calculation") + # because we have no direct relation in ContributionPlanBundle + # to ContributionPlan we have to consider this case + if class_name == "ContributionPlanBundle": + list_class.append("ContributionPlan") + return list_class @classmethod def convert(cls, instance, convert_from, convert_to, **kwargs): + if convert_from == "Policy": + cls._convert_policy(instance, convert_from, convert_to, **kwargs) + if convert_from == "Contract": + cls._convert_contract(instance, convert_from, convert_to, **kwargs) + + @classmethod + def _convert_policy(cls, instance, convert_from, convert_to, **kwargs): + pass + + @classmethod + def _convert_contract(cls, instance, convert_from, convert_to, **kwargs): pass diff --git a/calcrule_contribution_legacy/config.py b/calcrule_contribution_legacy/config.py index b9fd2a4..bce253a 100644 --- a/calcrule_contribution_legacy/config.py +++ b/calcrule_contribution_legacy/config.py @@ -1,4 +1,4 @@ -CLASS_RULE_PARAM_VALIDATION = None +CLASS_RULE_PARAM_VALIDATION = [] FROM_TO = [ {"from": "Policy", "to": "Invoice"}, diff --git a/calcrule_contribution_legacy/converters/__init__.py b/calcrule_contribution_legacy/converters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/calcrule_contribution_legacy/converters/policy_to_invoice.py b/calcrule_contribution_legacy/converters/policy_to_invoice.py new file mode 100644 index 0000000..49925c1 --- /dev/null +++ b/calcrule_contribution_legacy/converters/policy_to_invoice.py @@ -0,0 +1,79 @@ +from .policy_to_line_item import PolicyToLineItemConverter +from django.contrib.contenttypes.models import ContentType +from invoice.apps import InvoiceConfig +from invoice.services import InvoiceService +from invoice.models import Invoice + + +class PolicyToInvoiceConverter(object): + + @classmethod + def to_invoice_obj(cls, policy, user): + invoice_service = InvoiceService(user) + invoice = {} + cls.build_subject(policy, invoice) + cls.build_thirdparty(policy, invoice) + cls.build_code(policy, invoice) + cls.build_date_datas(policy, invoice) + #cls.build_tax_analysis(invoice) + cls.build_currency(invoice) + cls.build_status(invoice) + result = invoice_service.create(invoice) + if result["success"] is True: + # build invoice item + result_line = PolicyToLineItemConverter.to_invoice_line_item_obj( + policy=policy, + invoice_id=result["data"]["id"], + user =user + ) + if result_line["success"] is True: + # build invoice amounts based on invoice_line_item data + invoice_update = {} + invoice_update["id"] = result["data"]["id"] + cls.build_amounts(result_line["data"], invoice_update) + result_update = invoice_service.update(invoice_update) + return [result, result_line] + return [result] + + @classmethod + def build_subject(cls, policy, invoice): + invoice["subject_id"] = policy.family.id + invoice['subject_type'] = ContentType.objects.get_for_model(policy.family) + + @classmethod + def build_thirdparty(cls, policy, invoice): + invoice["thirdparty_id"] = policy.family.head_insuree.id + invoice['thirdparty_type'] = ContentType.objects.get_for_model(policy.family.head_insuree) + + @classmethod + def build_code(cls, policy, invoice): + invoice["code"] = f"" \ + f"IV-{policy.product.code}" \ + f"-{policy.family.head_insuree.chf_id}" \ + f"-{policy.start_date.strftime('%Y-%m')}" + + @classmethod + def build_date_datas(cls, policy, invoice): + invoice["date_due"] = policy.effective_date + invoice["date_invoice"] = policy.enroll_date + invoice["date_valid_from"] = policy.effective_date + invoice["date_valid_to"] = policy.expiry_date + + @classmethod + def build_tax_analysis(cls, invoice): + invoice["tax_analysis"] = None + + @classmethod + def build_currency(cls, invoice): + invoice["currency_tp_code"] = InvoiceConfig.default_currency_code + invoice["currency_code"] = InvoiceConfig.default_currency_code + + @classmethod + def build_status(cls, invoice): + invoice["status"] = Invoice.Status.VALIDATED + + @classmethod + def build_amounts(cls, line_item, invoice_update): + invoice_update["amount_net"] = line_item["amount_net"] + invoice_update["amount_total"] = line_item["amount_total"] + invoice_update["amount_discount"] = 0 if line_item["discount"] else line_item["discount"] diff --git a/calcrule_contribution_legacy/converters/policy_to_line_item.py b/calcrule_contribution_legacy/converters/policy_to_line_item.py new file mode 100644 index 0000000..7e096b4 --- /dev/null +++ b/calcrule_contribution_legacy/converters/policy_to_line_item.py @@ -0,0 +1,83 @@ +from invoice.services import InvoiceLineItemService +from django.contrib.contenttypes.models import ContentType +from policy.models import Policy + + +class PolicyToLineItemConverter(object): + + @classmethod + def to_invoice_line_item_obj(cls, policy, invoice_id, user): + invoice_line_item_service = InvoiceLineItemService(user) + invoice_line_item = {} + cls.build_invoice_fk(invoice_line_item, invoice_id) + cls.build_line_fk(invoice_line_item, policy) + cls.build_dates(invoice_line_item, policy) + cls.build_code(invoice_line_item, policy) + cls.build_description(invoice_line_item, policy) + cls.build_details(invoice_line_item, policy) + cls.build_ledger_account(invoice_line_item, policy) + cls.build_quantity(invoice_line_item) + cls.build_unit_price(invoice_line_item, policy) + cls.build_discount(invoice_line_item, policy) + #cls.build_tax(invoice_line_item) + cls.build_amounts(invoice_line_item, policy) + result = invoice_line_item_service.create(invoice_line_item) + return result + + @classmethod + def build_invoice_fk(cls, invoice_line_item, invoice_id): + invoice_line_item["invoice_id"] = invoice_id + + @classmethod + def build_line_fk(cls, invoice_line_item, policy): + invoice_line_item["line_id"] = policy.id + invoice_line_item['line_type'] = ContentType.objects.get_for_model(policy) + + @classmethod + def build_dates(cls, invoice_line_item, policy): + invoice_line_item["date_valid_from"] = policy.effective_date + invoice_line_item["date_valid_to"] = policy.expiry_date + + @classmethod + def build_code(cls, invoice_line_item, policy): + invoice_line_item["code"] = policy.product.code + + @classmethod + def build_description(cls, invoice_line_item, policy): + invoice_line_item["description"] = policy.product.name + + @classmethod + def build_details(cls, invoice_line_item, policy): + details = {"otherName": policy.family.head_insuree.other_names, "name": policy.family.head_insuree.last_name, + "gender": policy.family.head_insuree.gender.gender, "dob": f'{policy.family.head_insuree.dob}'} + invoice_line_item["details"] = details + + @classmethod + def build_ledger_account(cls, invoice_line_item, policy): + invoice_line_item["ledger_account"] = policy.product.acc_code_premiums + + @classmethod + def build_quantity(cls, invoice_line_item): + invoice_line_item["quantity"] = 1 + + @classmethod + def build_unit_price(cls, invoice_line_item, policy): + invoice_line_item["unit_price"] = policy.value + + @classmethod + def build_discount(cls, invoice_line_item, policy): + if policy.stage == Policy.STAGE_RENEWED: + invoice_line_item["discount"] = policy.preduct.renewal_discount_perc + + @classmethod + def build_tax(cls, invoice_line_item): + invoice_line_item["tax_rate"] = None + invoice_line_item["tax_analysis"] = None + + @classmethod + def build_amounts(cls, invoice_line_item, policy): + invoice_line_item["amount_net"] = invoice_line_item["quantity"] * invoice_line_item["unit_price"] + if "discount" in invoice_line_item: + invoice_discount = invoice_line_item["amount_net"] * invoice_line_item["discount"] + invoice_line_item["amount_net"] = invoice_line_item["amount_net"] - invoice_discount + invoice_line_item["amount_total"] = invoice_line_item["amount_net"] diff --git a/setup.py b/setup.py index 7aa8d63..46d9658 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,10 @@ 'django-db-signals', 'djangorestframework', 'openimis-be-core', + 'openimis-be-policy', + 'openimis-be-contract', 'openimis-be-calculation', + 'openimis-be-invoice', ], classifiers=[ 'Environment :: Web Environment', From ce007fa94bfdcfa6b04be94b59b9627bbc84fe84 Mon Sep 17 00:00:00 2001 From: sniedzielski Date: Wed, 3 Nov 2021 16:10:38 +0100 Subject: [PATCH 03/15] OPL-26: added conversion process with connecting post/pre signals --- .../calculation_rule.py | 57 +++++++++++++++---- calcrule_contribution_legacy/config.py | 2 +- .../converters/__init__.py | 2 + .../converters/policy_to_invoice.py | 22 +------ .../converters/policy_to_line_item.py | 12 +--- 5 files changed, 53 insertions(+), 42 deletions(-) diff --git a/calcrule_contribution_legacy/calculation_rule.py b/calcrule_contribution_legacy/calculation_rule.py index c3a46f0..43a5595 100644 --- a/calcrule_contribution_legacy/calculation_rule.py +++ b/calcrule_contribution_legacy/calculation_rule.py @@ -3,8 +3,8 @@ from .apps import AbsCalculationRule from .config import CLASS_RULE_PARAM_VALIDATION, \ DESCRIPTION_CONTRIBUTION_VALUATION, FROM_TO -from .converters.policy_to_invoice import PolicyToInvoiceConverter -from core.signals import Signal +from calcrule_contribution_legacy.converters import PolicyToInvoiceConverter, PolicyToLineItemConverter +from core.signals import * from core import datetime from django.contrib.contenttypes.models import ContentType from django.db.models.query import Q @@ -77,7 +77,10 @@ def check_calculation(cls, instance): @classmethod def calculate(cls, instance, *args): - pass + if instance.__class__.__name__ == "ContractDetails" or instance.__class__.__name__ == "Policy": + if instance.__class__.__name__ == "Policy": + pass + # policy_value = policy_values(policy=instance, family=instance.family, prev_policy=None) @classmethod def get_linked_class(cls, sender, class_name, **kwargs): @@ -102,16 +105,48 @@ def get_linked_class(cls, sender, class_name, **kwargs): return list_class @classmethod - def convert(cls, instance, convert_from, convert_to, **kwargs): - if convert_from == "Policy": - cls._convert_policy(instance, convert_from, convert_to, **kwargs) - if convert_from == "Contract": - cls._convert_contract(instance, convert_from, convert_to, **kwargs) + @register_service_signal('convert_to_invoice') + def convert(cls, instance, convert_to, **kwargs): + # check from signal before if invoice already exist for instance + results = {} + signal = REGISTERED_SERVICE_SIGNALS['convert_to_invoice'] + results_check_invoice_exist = signal.signal_results['before'][0][1] + if results_check_invoice_exist: + convert_from = instance.__class__.__name__ + if convert_from == "Policy": + results = cls._convert_policy(instance) + if convert_from == "ContractContributionPlanDetails": + results = cls._convert_contract(instance) + results['user'] = kwargs.get('user', None) + # after this method signal is sent to invoice module to save invoice data in db + return results @classmethod - def _convert_policy(cls, instance, convert_from, convert_to, **kwargs): - pass + def convert_batch(cls, **kwargs): + """ function specific for informal sector """ + # TODO Informal sector / from Policy to Invoice: this function will take the all polices + # related to the product (all product if not specified) + # that have no invoice and were created in the period specified in the specified location if any + function_arguments = kwargs.get('data')[1] + date_from = function_arguments.get('from_date', None) + date_to = function_arguments.get('to_date', None) + user = function_arguments.get('user', None) + product = function_arguments.get('product', None) + policies_covered = Policy.objects.filter( + Q(start_date__gte=date_from, start_date__lte=date_to, effective_date__isnull=False), + ).order_by('start_date') + if product: + policies_covered = policies_covered.filter(product__id=product) + # take all policies that have no invoice + for policy in policies_covered: + cls.run_convert(instance=policy, convert_to='Invoice', user=user) + + @classmethod + def _convert_policy(cls, instance): + invoice = PolicyToInvoiceConverter.to_invoice_obj(policy=instance) + invoice_line_item = PolicyToLineItemConverter.to_invoice_line_item_obj(policy=instance) + return {'invoice_data': invoice, 'invoice_data_line': invoice_line_item} @classmethod - def _convert_contract(cls, instance, convert_from, convert_to, **kwargs): + def _convert_contract(cls, instance, **kwargs): pass diff --git a/calcrule_contribution_legacy/config.py b/calcrule_contribution_legacy/config.py index bce253a..36b215f 100644 --- a/calcrule_contribution_legacy/config.py +++ b/calcrule_contribution_legacy/config.py @@ -2,7 +2,7 @@ FROM_TO = [ {"from": "Policy", "to": "Invoice"}, - {"from": "Contract", "to": "Invoice"} + {"from": "ContractContributionPlanDetails", "to": "InvoiceLine"} ] DESCRIPTION_CONTRIBUTION_VALUATION = F"" \ diff --git a/calcrule_contribution_legacy/converters/__init__.py b/calcrule_contribution_legacy/converters/__init__.py index e69de29..4c9bf39 100644 --- a/calcrule_contribution_legacy/converters/__init__.py +++ b/calcrule_contribution_legacy/converters/__init__.py @@ -0,0 +1,2 @@ +from calcrule_contribution_legacy.converters.policy_to_invoice import PolicyToInvoiceConverter +from calcrule_contribution_legacy.converters.policy_to_line_item import PolicyToLineItemConverter diff --git a/calcrule_contribution_legacy/converters/policy_to_invoice.py b/calcrule_contribution_legacy/converters/policy_to_invoice.py index 49925c1..bce19b3 100644 --- a/calcrule_contribution_legacy/converters/policy_to_invoice.py +++ b/calcrule_contribution_legacy/converters/policy_to_invoice.py @@ -1,15 +1,12 @@ -from .policy_to_line_item import PolicyToLineItemConverter from django.contrib.contenttypes.models import ContentType from invoice.apps import InvoiceConfig -from invoice.services import InvoiceService from invoice.models import Invoice class PolicyToInvoiceConverter(object): @classmethod - def to_invoice_obj(cls, policy, user): - invoice_service = InvoiceService(user) + def to_invoice_obj(cls, policy): invoice = {} cls.build_subject(policy, invoice) cls.build_thirdparty(policy, invoice) @@ -18,22 +15,7 @@ def to_invoice_obj(cls, policy, user): #cls.build_tax_analysis(invoice) cls.build_currency(invoice) cls.build_status(invoice) - result = invoice_service.create(invoice) - if result["success"] is True: - # build invoice item - result_line = PolicyToLineItemConverter.to_invoice_line_item_obj( - policy=policy, - invoice_id=result["data"]["id"], - user =user - ) - if result_line["success"] is True: - # build invoice amounts based on invoice_line_item data - invoice_update = {} - invoice_update["id"] = result["data"]["id"] - cls.build_amounts(result_line["data"], invoice_update) - result_update = invoice_service.update(invoice_update) - return [result, result_line] - return [result] + return invoice @classmethod def build_subject(cls, policy, invoice): diff --git a/calcrule_contribution_legacy/converters/policy_to_line_item.py b/calcrule_contribution_legacy/converters/policy_to_line_item.py index 7e096b4..5e0b627 100644 --- a/calcrule_contribution_legacy/converters/policy_to_line_item.py +++ b/calcrule_contribution_legacy/converters/policy_to_line_item.py @@ -1,4 +1,3 @@ -from invoice.services import InvoiceLineItemService from django.contrib.contenttypes.models import ContentType from policy.models import Policy @@ -6,10 +5,8 @@ class PolicyToLineItemConverter(object): @classmethod - def to_invoice_line_item_obj(cls, policy, invoice_id, user): - invoice_line_item_service = InvoiceLineItemService(user) + def to_invoice_line_item_obj(cls, policy): invoice_line_item = {} - cls.build_invoice_fk(invoice_line_item, invoice_id) cls.build_line_fk(invoice_line_item, policy) cls.build_dates(invoice_line_item, policy) cls.build_code(invoice_line_item, policy) @@ -21,12 +18,7 @@ def to_invoice_line_item_obj(cls, policy, invoice_id, user): cls.build_discount(invoice_line_item, policy) #cls.build_tax(invoice_line_item) cls.build_amounts(invoice_line_item, policy) - result = invoice_line_item_service.create(invoice_line_item) - return result - - @classmethod - def build_invoice_fk(cls, invoice_line_item, invoice_id): - invoice_line_item["invoice_id"] = invoice_id + return invoice_line_item @classmethod def build_line_fk(cls, invoice_line_item, policy): From 459bb256b891ec4487c7e4d07958426aa39df082 Mon Sep 17 00:00:00 2001 From: sniedzielski Date: Wed, 3 Nov 2021 16:12:50 +0100 Subject: [PATCH 04/15] OPL-26: undo calculate changes --- calcrule_contribution_legacy/calculation_rule.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/calcrule_contribution_legacy/calculation_rule.py b/calcrule_contribution_legacy/calculation_rule.py index 43a5595..18ab7f9 100644 --- a/calcrule_contribution_legacy/calculation_rule.py +++ b/calcrule_contribution_legacy/calculation_rule.py @@ -77,10 +77,7 @@ def check_calculation(cls, instance): @classmethod def calculate(cls, instance, *args): - if instance.__class__.__name__ == "ContractDetails" or instance.__class__.__name__ == "Policy": - if instance.__class__.__name__ == "Policy": - pass - # policy_value = policy_values(policy=instance, family=instance.family, prev_policy=None) + pass @classmethod def get_linked_class(cls, sender, class_name, **kwargs): From 9ab4acf6dd5d5b8f84df859cf7de988d0ed35b4a Mon Sep 17 00:00:00 2001 From: sniedzielski Date: Mon, 8 Nov 2021 15:12:20 +0100 Subject: [PATCH 05/15] OPL-31: added converter mechanism for contract items --- .../calculation_rule.py | 35 ++++++-- .../converters/__init__.py | 2 + .../contract_cpd_to_invoice_line_item.py | 79 +++++++++++++++++++ .../converters/contract_to_invoice.py | 52 ++++++++++++ .../converters/policy_to_line_item.py | 2 +- calcrule_contribution_legacy/signals.py | 22 ++++++ 6 files changed, 185 insertions(+), 7 deletions(-) create mode 100644 calcrule_contribution_legacy/converters/contract_cpd_to_invoice_line_item.py create mode 100644 calcrule_contribution_legacy/converters/contract_to_invoice.py create mode 100644 calcrule_contribution_legacy/signals.py diff --git a/calcrule_contribution_legacy/calculation_rule.py b/calcrule_contribution_legacy/calculation_rule.py index 18ab7f9..00633d3 100644 --- a/calcrule_contribution_legacy/calculation_rule.py +++ b/calcrule_contribution_legacy/calculation_rule.py @@ -3,7 +3,8 @@ from .apps import AbsCalculationRule from .config import CLASS_RULE_PARAM_VALIDATION, \ DESCRIPTION_CONTRIBUTION_VALUATION, FROM_TO -from calcrule_contribution_legacy.converters import PolicyToInvoiceConverter, PolicyToLineItemConverter +from calcrule_contribution_legacy.converters import PolicyToInvoiceConverter, PolicyToLineItemConverter, \ + ContractToInvoiceConverter, ContractCpdToLineItemConverter from core.signals import * from core import datetime from django.contrib.contenttypes.models import ContentType @@ -77,7 +78,12 @@ def check_calculation(cls, instance): @classmethod def calculate(cls, instance, *args): - pass + class_name = instance.__class__.__name__ + if class_name == "ContractDetails": + #policy_values + pass + if class_name == "Policy": + pass @classmethod def get_linked_class(cls, sender, class_name, **kwargs): @@ -112,8 +118,9 @@ def convert(cls, instance, convert_to, **kwargs): convert_from = instance.__class__.__name__ if convert_from == "Policy": results = cls._convert_policy(instance) - if convert_from == "ContractContributionPlanDetails": - results = cls._convert_contract(instance) + if convert_from == "Contract": + ccpd_list = kwargs.get('ccpd_list', None) + results = cls._convert_contract(instance, ccpd_list=ccpd_list) results['user'] = kwargs.get('user', None) # after this method signal is sent to invoice module to save invoice data in db return results @@ -142,8 +149,24 @@ def convert_batch(cls, **kwargs): def _convert_policy(cls, instance): invoice = PolicyToInvoiceConverter.to_invoice_obj(policy=instance) invoice_line_item = PolicyToLineItemConverter.to_invoice_line_item_obj(policy=instance) - return {'invoice_data': invoice, 'invoice_data_line': invoice_line_item} + return { + 'invoice_data': invoice, + 'invoice_data_line': invoice_line_item, + 'type_conversion': 'policy-invoice' + } @classmethod def _convert_contract(cls, instance, **kwargs): - pass + invoice = ContractToInvoiceConverter.to_invoice_obj(contract=instance) + ccpd_list = kwargs.get('ccpd_list', None) + invoice_line_item = [] + if ccpd_list: + for ccpd in ccpd_list: + invoice_line_item.append( + ContractCpdToLineItemConverter.to_invoice_line_item_obj(contract_cpd=ccpd) + ) + return { + 'invoice_data': invoice, + 'invoice_data_line': invoice_line_item, + 'type_conversion': 'contract-invoice' + } diff --git a/calcrule_contribution_legacy/converters/__init__.py b/calcrule_contribution_legacy/converters/__init__.py index 4c9bf39..ddae1cc 100644 --- a/calcrule_contribution_legacy/converters/__init__.py +++ b/calcrule_contribution_legacy/converters/__init__.py @@ -1,2 +1,4 @@ from calcrule_contribution_legacy.converters.policy_to_invoice import PolicyToInvoiceConverter from calcrule_contribution_legacy.converters.policy_to_line_item import PolicyToLineItemConverter +from calcrule_contribution_legacy.converters.contract_to_invoice import ContractToInvoiceConverter +from calcrule_contribution_legacy.converters.contract_cpd_to_invoice_line_item import ContractCpdToLineItemConverter diff --git a/calcrule_contribution_legacy/converters/contract_cpd_to_invoice_line_item.py b/calcrule_contribution_legacy/converters/contract_cpd_to_invoice_line_item.py new file mode 100644 index 0000000..1533707 --- /dev/null +++ b/calcrule_contribution_legacy/converters/contract_cpd_to_invoice_line_item.py @@ -0,0 +1,79 @@ +from django.contrib.contenttypes.models import ContentType +from policy.models import Policy + + +class ContractCpdToLineItemConverter(object): + + @classmethod + def to_invoice_line_item_obj(cls, contract_cpd): + invoice_line_item = {} + cls.build_line_fk(invoice_line_item, contract_cpd) + cls.build_dates(invoice_line_item, contract_cpd) + cls.build_code(invoice_line_item, contract_cpd) + cls.build_description(invoice_line_item, contract_cpd) + cls.build_details(invoice_line_item, contract_cpd) + cls.build_ledger_account(invoice_line_item, contract_cpd) + cls.build_quantity(invoice_line_item) + cls.build_unit_price(invoice_line_item, contract_cpd) + cls.build_discount(invoice_line_item, contract_cpd) + #cls.build_tax(invoice_line_item) + cls.build_amounts(invoice_line_item, contract_cpd) + return invoice_line_item + + @classmethod + def build_line_fk(cls, invoice_line_item, contract_cpd): + invoice_line_item["line_id"] = contract_cpd.id + invoice_line_item['line_type'] = ContentType.objects.get_for_model(contract_cpd) + + @classmethod + def build_dates(cls, invoice_line_item, contract_cpd): + invoice_line_item["date_valid_from"] = contract_cpd.date_valid_from + invoice_line_item["date_valid_to"] = contract_cpd.date_valid_to + + @classmethod + def build_code(cls, invoice_line_item, contract_cpd): + invoice_line_item["code"] = contract_cpd.contribution_plan.benefit_plan.code + + @classmethod + def build_description(cls, invoice_line_item, contract_cpd): + invoice_line_item["description"] = contract_cpd.contribution_plan.benefit_plan.name + + @classmethod + def build_details(cls, invoice_line_item, contract_cpd): + policy = contract_cpd.policy + details = {"otherName": policy.family.head_insuree.other_names, "name": policy.family.head_insuree.last_name, + "gender": policy.family.head_insuree.gender.gender, "dob": f'{policy.family.head_insuree.dob}'} + invoice_line_item["details"] = details + + @classmethod + def build_ledger_account(cls, invoice_line_item, contract_cpd): + invoice_line_item["ledger_account"] = contract_cpd.contribution_plan.benefit_plan.acc_code_premiums + + @classmethod + def build_quantity(cls, invoice_line_item): + invoice_line_item["quantity"] = 1 + + @classmethod + def build_unit_price(cls, invoice_line_item, contract_cpd): + contribution = contract_cpd.contribution + # take the amount calculated by calculation rule + invoice_line_item["unit_price"] = contribution.amount + + @classmethod + def build_discount(cls, invoice_line_item, contract_cpd): + policy = contract_cpd.policy + if policy.stage == Policy.STAGE_RENEWED: + invoice_line_item["discount"] = policy.product.renewal_discount_perc + + @classmethod + def build_tax(cls, invoice_line_item): + invoice_line_item["tax_rate"] = None + invoice_line_item["tax_analysis"] = None + + @classmethod + def build_amounts(cls, invoice_line_item, contract_cpd): + invoice_line_item["amount_net"] = invoice_line_item["quantity"] * invoice_line_item["unit_price"] + if "discount" in invoice_line_item: + invoice_discount = invoice_line_item["amount_net"] * invoice_line_item["discount"] + invoice_line_item["amount_net"] = invoice_line_item["amount_net"] - invoice_discount + invoice_line_item["amount_total"] = invoice_line_item["amount_net"] diff --git a/calcrule_contribution_legacy/converters/contract_to_invoice.py b/calcrule_contribution_legacy/converters/contract_to_invoice.py new file mode 100644 index 0000000..6875800 --- /dev/null +++ b/calcrule_contribution_legacy/converters/contract_to_invoice.py @@ -0,0 +1,52 @@ +from django.contrib.contenttypes.models import ContentType +from invoice.apps import InvoiceConfig +from invoice.models import Invoice + + +class ContractToInvoiceConverter(object): + + @classmethod + def to_invoice_obj(cls, contract): + invoice = {} + cls.build_subject(contract, invoice) + cls.build_thirdparty(contract, invoice) + cls.build_code(contract, invoice) + cls.build_date_datas(contract, invoice) + #cls.build_tax_analysis(invoice) + cls.build_currency(invoice) + cls.build_status(invoice) + return invoice + + @classmethod + def build_subject(cls, contract, invoice): + invoice["subject_id"] = contract.id + invoice['subject_type'] = ContentType.objects.get_for_model(contract) + + @classmethod + def build_thirdparty(cls, contract, invoice): + invoice["thirdparty_id"] = contract.policy_holder.id + invoice['thirdparty_type'] = ContentType.objects.get_for_model(contract.policy_holder) + + @classmethod + def build_code(cls, contract, invoice): + invoice["code"] = f"IV-{contract.code}" + + @classmethod + def build_date_datas(cls, contract, invoice): + invoice["date_due"] = contract.date_payment_due + invoice["date_invoice"] = contract.date_approved + invoice["date_valid_from"] = contract.date_valid_from + invoice["date_valid_to"] = contract.date_valid_to + + @classmethod + def build_tax_analysis(cls, invoice): + invoice["tax_analysis"] = None + + @classmethod + def build_currency(cls, invoice): + invoice["currency_tp_code"] = InvoiceConfig.default_currency_code + invoice["currency_code"] = InvoiceConfig.default_currency_code + + @classmethod + def build_status(cls, invoice): + invoice["status"] = Invoice.Status.VALIDATED diff --git a/calcrule_contribution_legacy/converters/policy_to_line_item.py b/calcrule_contribution_legacy/converters/policy_to_line_item.py index 5e0b627..2bb2d29 100644 --- a/calcrule_contribution_legacy/converters/policy_to_line_item.py +++ b/calcrule_contribution_legacy/converters/policy_to_line_item.py @@ -59,7 +59,7 @@ def build_unit_price(cls, invoice_line_item, policy): @classmethod def build_discount(cls, invoice_line_item, policy): if policy.stage == Policy.STAGE_RENEWED: - invoice_line_item["discount"] = policy.preduct.renewal_discount_perc + invoice_line_item["discount"] = policy.product.renewal_discount_perc @classmethod def build_tax(cls, invoice_line_item): diff --git a/calcrule_contribution_legacy/signals.py b/calcrule_contribution_legacy/signals.py new file mode 100644 index 0000000..570a381 --- /dev/null +++ b/calcrule_contribution_legacy/signals.py @@ -0,0 +1,22 @@ +from core.signals import bind_service_signal +from core.service_signals import ServiceSignalBindType +from calcrule_contribution_legacy.calculation_rule import ContributionPlanCalculationRuleProductModeling + + +def bind_service_signals(): + bind_service_signal( + 'create_invoice_from_contract', + adapt_signal_function_to_run_conversion_contract, + bind_type=ServiceSignalBindType.BEFORE + ) + + +def adapt_signal_function_to_run_conversion_contract(**kwargs): + # here there is adapter function to adapt signal result + # to the run_convert function arguments + passed_argument = kwargs.get('data', None) + if passed_argument: + result_conversion = ContributionPlanCalculationRuleProductModeling.run_convert( + **passed_argument[1] + ) + return result_conversion From cea6aa7bcd82258bebeadc9a8e47fc6095362408 Mon Sep 17 00:00:00 2001 From: sniedzielski Date: Tue, 9 Nov 2021 10:50:47 +0100 Subject: [PATCH 06/15] OPL-31: updated 'calculate' method --- calcrule_contribution_legacy/calculation_rule.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/calcrule_contribution_legacy/calculation_rule.py b/calcrule_contribution_legacy/calculation_rule.py index 00633d3..6feb5d1 100644 --- a/calcrule_contribution_legacy/calculation_rule.py +++ b/calcrule_contribution_legacy/calculation_rule.py @@ -79,11 +79,10 @@ def check_calculation(cls, instance): @classmethod def calculate(cls, instance, *args): class_name = instance.__class__.__name__ - if class_name == "ContractDetails": - #policy_values - pass + if class_name == "ContractContributionPlanDetails": + return policy_values(instance.policy, instance.policy.family, None) if class_name == "Policy": - pass + return policy_values(instance, instance.family, None) @classmethod def get_linked_class(cls, sender, class_name, **kwargs): @@ -151,7 +150,7 @@ def _convert_policy(cls, instance): invoice_line_item = PolicyToLineItemConverter.to_invoice_line_item_obj(policy=instance) return { 'invoice_data': invoice, - 'invoice_data_line': invoice_line_item, + 'invoice_data_line': [invoice_line_item], 'type_conversion': 'policy-invoice' } From 0915e852253fcc8535a9db26c3adb86016b86751 Mon Sep 17 00:00:00 2001 From: sniedzielski Date: Wed, 10 Nov 2021 11:51:51 +0100 Subject: [PATCH 07/15] OPL-31: changed name of rule --- calcrule_contribution_legacy/calculation_rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calcrule_contribution_legacy/calculation_rule.py b/calcrule_contribution_legacy/calculation_rule.py index 6feb5d1..7c736d6 100644 --- a/calcrule_contribution_legacy/calculation_rule.py +++ b/calcrule_contribution_legacy/calculation_rule.py @@ -16,7 +16,7 @@ class ContributionPlanCalculationRuleProductModeling(AbsCalculationRule): version = 1 uuid = "2aee6d54-eef4-4ee6-1c47-2793cfa5f9a8" - calculation_rule_name = "payment: fee for service" + calculation_rule_name = "Contribution: legacy" description = DESCRIPTION_CONTRIBUTION_VALUATION impacted_class_parameter = CLASS_RULE_PARAM_VALIDATION date_valid_from = datetime.datetime(2000, 1, 1) From 8c15dd8d335d5cf52c7fb9167feb3c6882f4cde7 Mon Sep 17 00:00:00 2001 From: Patrick Delcroix Date: Mon, 15 Nov 2021 16:56:44 +0100 Subject: [PATCH 08/15] Create django.yml --- .github/workflows/django.yml | 100 +++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 .github/workflows/django.yml diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml new file mode 100644 index 0000000..18f0751 --- /dev/null +++ b/.github/workflows/django.yml @@ -0,0 +1,100 @@ +name: Automated CI testing +# This workflow run automatically for every commit on github it checks the syntax and launch the tests. +# | grep . | uniq -c filters out empty lines and then groups consecutive lines together with the number of occurrences +on: + pull_request: + workflow_dispatch: + inputs: + comment: + description: Just a simple comment to know the purpose of the manual build + required: false + +jobs: + run_test: + runs-on: ubuntu-latest + services: + mssql: + image: mcr.microsoft.com/mssql/server:2017-latest + env: + ACCEPT_EULA: Y + SA_PASSWORD: GitHub999 + ports: + - 1433:1433 + # needed because the mssql container does not provide a health check + options: --health-interval=10s --health-timeout=3s --health-start-period=10s --health-retries=10 --health-cmd="/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P ${SA_PASSWORD} -Q 'SELECT 1' || exit 1" + + steps: + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: install linux packages + run: | + wget https://raw.githubusercontent.com/openimis/database_ms_sqlserver/develop/Empty%20databases/openIMIS_ONLINE.sql -O openIMIS_ONLINE.sql + wget https://raw.githubusercontent.com/openimis/database_ms_sqlserver/develop/Demo%20database/openIMIS_demo_ONLINE.sql -O openIMIS_demo_ONLINE.sql + curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - + curl https://packages.microsoft.com/config/ubuntu/20.04/prod.list | sudo tee /etc/apt/sources.list.d/msprod.list + sudo apt-get update + sudo ACCEPT_EULA=Y apt-get install -y mssql-tools build-essential dialog apt-utils unixodbc-dev -y + python -m pip install --upgrade pip + - name: pull openimis backend + run: | + rm ./openimis -rf + git clone --depth 1 --branch develop https://github.com/openimis/openimis-be_py.git ./openimis + - name: copy current branch + uses: actions/checkout@v2 + with: + path: './current-module' + - name: Update the configuration + working-directory: ./openimis + run: | + export MODULE_NAME="$(echo $GITHUB_REPOSITORY | sed 's#^openimis/openimis-be-\(.*\)_py$#\1#')" + echo "the local module called $MODULE_NAME will be injected in openIMIS .json" + echo $(jq "(.modules[] | select(.name == \"$MODULE_NAME\") | .pip)|=\"../current-module\"" openimis.json) > openimis.json + cat openimis.json + - name: Install openIMIS Python dependencies + working-directory: ./openimis + run: | + pip install -r requirements.txt + python modules-requirements.py openimis.json > modules-requirements.txt + cat modules-requirements.txt + pip install -r modules-requirements.txt + - name: Environment info + working-directory: ./openimis + run: | + pip list + - name: Initialize DB + run: | + /opt/mssql-tools/bin/sqlcmd -S localhost,1433 -U SA -P $SA_PASSWORD -Q 'DROP DATABASE IF EXISTS imis' + /opt/mssql-tools/bin/sqlcmd -S localhost,1433 -U SA -P $SA_PASSWORD -Q 'CREATE DATABASE imis' + /opt/mssql-tools/bin/sqlcmd -S localhost,1433 -U SA -P $SA_PASSWORD -d imis -i openIMIS_ONLINE.sql | grep . | uniq -c + /opt/mssql-tools/bin/sqlcmd -S localhost,1433 -U SA -P $SA_PASSWORD -d imis -i openIMIS_demo_ONLINE.sql | grep . | uniq -c + env: + SA_PASSWORD: GitHub999 + ACCEPT_EULA: Y + +# - name: Check formatting with black +# run: | +# black --check . + + - name: Django tests + working-directory: ./openimis/openIMIS + run: | + export MODULE_NAME="$(echo $GITHUB_REPOSITORY | sed 's#^openimis/openimis-be-\(.*\)_py$#\1#')" + python -V + ls -l + python manage.py migrate + python init_test_db.py | grep . | uniq -c + python manage.py test --keepdb $MODULE_NAME + env: + SECRET_KEY: secret + DEBUG: true + #DJANGO_SETTINGS_MODULE: hat.settings + DB_HOST: localhost + DB_PORT: 1433 + DB_NAME: imis + DB_USER: sa + DB_PASSWORD: GitHub999 + #DEV_SERVER: true + SITE_ROOT: api + REMOTE_USER_AUTHENTICATION: True From 8da1adfea6c98abd58dead7a98064bec669fd3ff Mon Sep 17 00:00:00 2001 From: Patrick Delcroix Date: Mon, 15 Nov 2021 16:58:20 +0100 Subject: [PATCH 09/15] add publish --- .github/workflows/python-publish.yml | 38 ++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/python-publish.yml diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..4b00014 --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,38 @@ +# This workflows will upload a Python Package using Twine when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +name: Upload Python Package + +on: + release: + types: [created] + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: olegtarasov/get-tag@v2.1 + id: tagName + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine jq + + - name: update setup.py + run: | + echo "tag to use $GIT_TAG_NAME" + sed -i "s/version='.*'/version='$GIT_TAG_NAME'/g" setup.py + - name: Build and publish + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{secrets.PYPI_TOKEN}} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* From 702ffea631695d058f953883312cb16b6caa0fbb Mon Sep 17 00:00:00 2001 From: sniedzielski Date: Tue, 11 Jan 2022 15:08:37 +0100 Subject: [PATCH 10/15] OPL-52: hotfix - added default values for type and subtype args --- calcrule_contribution_legacy/calculation_rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calcrule_contribution_legacy/calculation_rule.py b/calcrule_contribution_legacy/calculation_rule.py index 7c736d6..a530dee 100644 --- a/calcrule_contribution_legacy/calculation_rule.py +++ b/calcrule_contribution_legacy/calculation_rule.py @@ -49,7 +49,7 @@ def ready(cls): cls.signal_convert_from_to.connect(cls.run_convert, dispatch_uid="on_convert_from_to") @classmethod - def active_for_object(cls, instance, context, type, sub_type): + def active_for_object(cls, instance, context, type="account_receivable", sub_type="contribution"): return instance.__class__.__name__ == "ContributionPlan" \ and context in ["submit"] \ and cls.check_calculation(instance) From 1164c5b0391c2a6114ece0c3464b20c8540c5f07 Mon Sep 17 00:00:00 2001 From: Patrick Delcroix Date: Mon, 17 Jan 2022 13:22:38 +0100 Subject: [PATCH 11/15] Update python-publish.yml --- .github/workflows/python-publish.yml | 125 ++++++++++++++++++++------- 1 file changed, 93 insertions(+), 32 deletions(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 4b00014..49f5ca4 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -1,38 +1,99 @@ -# This workflows will upload a Python Package using Twine when a release is created -# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries - -name: Upload Python Package - +name: Automated CI testing +# This workflow run automatically for every commit on github it checks the syntax and launch the tests. +# | grep . | uniq -c filters out empty lines and then groups consecutive lines together with the number of occurrences on: - release: - types: [created] + pull_request: + workflow_dispatch: + inputs: + comment: + description: Just a simple comment to know the purpose of the manual build + required: false jobs: - deploy: - + run_test: runs-on: ubuntu-latest + services: + mssql: + image: mcr.microsoft.com/mssql/server:2017-latest + env: + ACCEPT_EULA: Y + SA_PASSWORD: GitHub999 + ports: + - 1433:1433 + # needed because the mssql container does not provide a health check + options: --health-interval=10s --health-timeout=3s --health-start-period=10s --health-retries=10 --health-cmd="/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P ${SA_PASSWORD} -Q 'SELECT 1' || exit 1" steps: - - uses: olegtarasov/get-tag@v2.1 - id: tagName - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine jq - - - name: update setup.py - run: | - echo "tag to use $GIT_TAG_NAME" - sed -i "s/version='.*'/version='$GIT_TAG_NAME'/g" setup.py - - name: Build and publish - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{secrets.PYPI_TOKEN}} - run: | - python setup.py sdist bdist_wheel - twine upload dist/* + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: install linux packages + run: | + git clone --depth 1 --branch develop https://github.com/openimis/database_ms_sqlserver.git ./sql + cd sql/ && bash concatenate_files.sh && cd .. + curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - + curl https://packages.microsoft.com/config/ubuntu/20.04/prod.list | sudo tee /etc/apt/sources.list.d/msprod.list + sudo apt-get update + sudo ACCEPT_EULA=Y apt-get install -y mssql-tools build-essential dialog apt-utils unixodbc-dev -y + python -m pip install --upgrade pip + - name: pull openimis backend + run: | + rm ./openimis -rf + git clone --depth 1 --branch develop https://github.com/openimis/openimis-be_py.git ./openimis + - name: copy current branch + uses: actions/checkout@v2 + with: + path: './current-module' + - name: Update the configuration + working-directory: ./openimis + run: | + export MODULE_NAME="$(echo $GITHUB_REPOSITORY | sed 's#^openimis/openimis-be-\(.*\)_py$#\1#')" + echo "the local module called $MODULE_NAME will be injected in openIMIS .json" + jq --arg name "$MODULE_NAME" 'if [.modules[].name == ($name)]| max then (.modules[] | select(.name == ($name)) | .pip)|="../current-module" else .modules |= .+ [{name:($name), pip:"../current-module"}] end' openimis.json + echo $(jq --arg name "$MODULE_NAME" 'if [.modules[].name == ($name)]| max then (.modules[] | select(.name == ($name)) | .pip)|="../current-module" else .modules |= .+ [{name:($name), pip:"../current-module"}] end' openimis.json) > openimis.json + - name: Install openIMIS Python dependencies + working-directory: ./openimis + run: | + pip install -r requirements.txt + python modules-requirements.py openimis.json > modules-requirements.txt + cat modules-requirements.txt + pip install -r modules-requirements.txt + - name: Environment info + working-directory: ./openimis + run: | + pip list + - name: Initialize DB + run: | + /opt/mssql-tools/bin/sqlcmd -S localhost,1433 -U SA -P $SA_PASSWORD -Q 'DROP DATABASE IF EXISTS imis' + /opt/mssql-tools/bin/sqlcmd -S localhost,1433 -U SA -P $SA_PASSWORD -Q 'CREATE DATABASE imis' + /opt/mssql-tools/bin/sqlcmd -S localhost,1433 -U SA -P $SA_PASSWORD -d imis -i sql/output/fullDemoDatabase.sql | grep . | uniq -c + env: + SA_PASSWORD: GitHub999 + ACCEPT_EULA: Y + +# - name: Check formatting with black +# run: | +# black --check . + + - name: Django tests + working-directory: ./openimis/openIMIS + run: | + export MODULE_NAME="$(echo $GITHUB_REPOSITORY | sed 's#^openimis/openimis-be-\(.*\)_py$#\1#')" + python -V + ls -l + python manage.py migrate + python init_test_db.py | grep . | uniq -c + python manage.py test --keepdb $MODULE_NAME + env: + SECRET_KEY: secret + DEBUG: true + #DJANGO_SETTINGS_MODULE: hat.settings + DB_HOST: localhost + DB_PORT: 1433 + DB_NAME: imis + DB_USER: sa + DB_PASSWORD: GitHub999 + #DEV_SERVER: true + SITE_ROOT: api + REMOTE_USER_AUTHENTICATION: True From 9974962a3592c51e292730e2a8657e6ac596151f Mon Sep 17 00:00:00 2001 From: Patrick Delcroix Date: Tue, 22 Mar 2022 17:42:38 +0100 Subject: [PATCH 12/15] Update django.yml --- .github/workflows/django.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml index 18f0751..15a80ea 100644 --- a/.github/workflows/django.yml +++ b/.github/workflows/django.yml @@ -30,8 +30,8 @@ jobs: python-version: 3.8 - name: install linux packages run: | - wget https://raw.githubusercontent.com/openimis/database_ms_sqlserver/develop/Empty%20databases/openIMIS_ONLINE.sql -O openIMIS_ONLINE.sql - wget https://raw.githubusercontent.com/openimis/database_ms_sqlserver/develop/Demo%20database/openIMIS_demo_ONLINE.sql -O openIMIS_demo_ONLINE.sql + git clone --depth 1 --branch develop https://github.com/openimis/database_ms_sqlserver.git ./sql + cd sql/ && bash concatenate_files.sh && cd .. curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - curl https://packages.microsoft.com/config/ubuntu/20.04/prod.list | sudo tee /etc/apt/sources.list.d/msprod.list sudo apt-get update @@ -50,8 +50,8 @@ jobs: run: | export MODULE_NAME="$(echo $GITHUB_REPOSITORY | sed 's#^openimis/openimis-be-\(.*\)_py$#\1#')" echo "the local module called $MODULE_NAME will be injected in openIMIS .json" - echo $(jq "(.modules[] | select(.name == \"$MODULE_NAME\") | .pip)|=\"../current-module\"" openimis.json) > openimis.json - cat openimis.json + jq --arg name "$MODULE_NAME" 'if [.modules[].name == ($name)]| max then (.modules[] | select(.name == ($name)) | .pip)|="../current-module" else .modules |= .+ [{name:($name), pip:"../current-module"}] end' openimis.json + echo $(jq --arg name "$MODULE_NAME" 'if [.modules[].name == ($name)]| max then (.modules[] | select(.name == ($name)) | .pip)|="../current-module" else .modules |= .+ [{name:($name), pip:"../current-module"}] end' openimis.json) > openimis.json - name: Install openIMIS Python dependencies working-directory: ./openimis run: | @@ -67,8 +67,7 @@ jobs: run: | /opt/mssql-tools/bin/sqlcmd -S localhost,1433 -U SA -P $SA_PASSWORD -Q 'DROP DATABASE IF EXISTS imis' /opt/mssql-tools/bin/sqlcmd -S localhost,1433 -U SA -P $SA_PASSWORD -Q 'CREATE DATABASE imis' - /opt/mssql-tools/bin/sqlcmd -S localhost,1433 -U SA -P $SA_PASSWORD -d imis -i openIMIS_ONLINE.sql | grep . | uniq -c - /opt/mssql-tools/bin/sqlcmd -S localhost,1433 -U SA -P $SA_PASSWORD -d imis -i openIMIS_demo_ONLINE.sql | grep . | uniq -c + /opt/mssql-tools/bin/sqlcmd -S localhost,1433 -U SA -P $SA_PASSWORD -d imis -i sql/output/fullDemoDatabase.sql | grep . | uniq -c env: SA_PASSWORD: GitHub999 ACCEPT_EULA: Y @@ -97,4 +96,3 @@ jobs: DB_PASSWORD: GitHub999 #DEV_SERVER: true SITE_ROOT: api - REMOTE_USER_AUTHENTICATION: True From c29cb463d520188563b0814c496b0963c00b6664 Mon Sep 17 00:00:00 2001 From: sniedzielski Date: Wed, 30 Mar 2022 10:51:24 +0200 Subject: [PATCH 13/15] OPL-84: fixed type/subtype in calcrule --- calcrule_contribution_legacy/calculation_rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calcrule_contribution_legacy/calculation_rule.py b/calcrule_contribution_legacy/calculation_rule.py index a530dee..40d301f 100644 --- a/calcrule_contribution_legacy/calculation_rule.py +++ b/calcrule_contribution_legacy/calculation_rule.py @@ -16,7 +16,7 @@ class ContributionPlanCalculationRuleProductModeling(AbsCalculationRule): version = 1 uuid = "2aee6d54-eef4-4ee6-1c47-2793cfa5f9a8" - calculation_rule_name = "Contribution: legacy" + calculation_rule_name = "CV: legacy" description = DESCRIPTION_CONTRIBUTION_VALUATION impacted_class_parameter = CLASS_RULE_PARAM_VALIDATION date_valid_from = datetime.datetime(2000, 1, 1) From 7dbfaac80c8ad16f03565aa3857f7b57b881b933 Mon Sep 17 00:00:00 2001 From: sniedzielski Date: Fri, 22 Apr 2022 13:10:53 +0200 Subject: [PATCH 14/15] OPL-42: changes necessary to connect signal policy created to have Invoice --- .../calculation_rule.py | 36 +++++++++++-------- .../converters/policy_to_invoice.py | 4 +-- .../converters/policy_to_line_item.py | 10 +++--- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/calcrule_contribution_legacy/calculation_rule.py b/calcrule_contribution_legacy/calculation_rule.py index 40d301f..16fd4eb 100644 --- a/calcrule_contribution_legacy/calculation_rule.py +++ b/calcrule_contribution_legacy/calculation_rule.py @@ -1,16 +1,16 @@ -import json +from django.contrib.contenttypes.models import ContentType +from django.db.models.query import Q -from .apps import AbsCalculationRule -from .config import CLASS_RULE_PARAM_VALIDATION, \ +from calcrule_contribution_legacy.apps import AbsCalculationRule +from calcrule_contribution_legacy.config import CLASS_RULE_PARAM_VALIDATION, \ DESCRIPTION_CONTRIBUTION_VALUATION, FROM_TO from calcrule_contribution_legacy.converters import PolicyToInvoiceConverter, PolicyToLineItemConverter, \ ContractToInvoiceConverter, ContractCpdToLineItemConverter + +from core.models import User from core.signals import * from core import datetime -from django.contrib.contenttypes.models import ContentType -from django.db.models.query import Q from policy.models import Policy -from policy.values import policy_values class ContributionPlanCalculationRuleProductModeling(AbsCalculationRule): @@ -50,16 +50,18 @@ def ready(cls): @classmethod def active_for_object(cls, instance, context, type="account_receivable", sub_type="contribution"): - return instance.__class__.__name__ == "ContributionPlan" \ - and context in ["submit"] \ + return instance.__class__.__name__ in ["ContributionPlan", "Policy"] \ + and context in ["submit", "PolicyCreatedInvoice", "ContractCreated"] \ and cls.check_calculation(instance) @classmethod def check_calculation(cls, instance): class_name = instance.__class__.__name__ match = False - if class_name == "ContributionPlan": - match = cls.uuid == instance.calculation + if class_name == "ABCMeta": + match = str(cls.uuid) == str(instance.uuid) + elif class_name == "ContributionPlan": + match = str(cls.uuid) == str(instance.calculation) elif class_name == "PolicyHolderInsuree": match = cls.check_calculation(instance.cpb) elif class_name == "ContractDetails": @@ -71,18 +73,22 @@ def check_calculation(cls, instance): if cls.check_calculation(cp): match = True break + elif class_name == "Policy": + match = cls.check_calculation(instance.family) # for legacy the calculation is valid for all famillies elif class_name == "Family": match = True return match @classmethod - def calculate(cls, instance, *args): + def calculate(cls, instance, **kwargs): + context = kwargs.get('context', None) + user = kwargs.get('user', None) + if user is None: + user = User.objects.filter(i_user__id=instance.audit_user_id).first() class_name = instance.__class__.__name__ - if class_name == "ContractContributionPlanDetails": - return policy_values(instance.policy, instance.policy.family, None) - if class_name == "Policy": - return policy_values(instance, instance.family, None) + cls.run_convert(instance=instance, convert_to='Invoice', user=user) + return f"conversion finished {cls.calculation_rule_name}" @classmethod def get_linked_class(cls, sender, class_name, **kwargs): diff --git a/calcrule_contribution_legacy/converters/policy_to_invoice.py b/calcrule_contribution_legacy/converters/policy_to_invoice.py index bce19b3..b60d8cc 100644 --- a/calcrule_contribution_legacy/converters/policy_to_invoice.py +++ b/calcrule_contribution_legacy/converters/policy_to_invoice.py @@ -36,9 +36,9 @@ def build_code(cls, policy, invoice): @classmethod def build_date_datas(cls, policy, invoice): - invoice["date_due"] = policy.effective_date + invoice["date_due"] = policy.effective_date if policy.effective_date else policy.enroll_date invoice["date_invoice"] = policy.enroll_date - invoice["date_valid_from"] = policy.effective_date + invoice["date_valid_from"] = policy.effective_date if policy.effective_date else policy.enroll_date invoice["date_valid_to"] = policy.expiry_date @classmethod diff --git a/calcrule_contribution_legacy/converters/policy_to_line_item.py b/calcrule_contribution_legacy/converters/policy_to_line_item.py index 2bb2d29..0218cec 100644 --- a/calcrule_contribution_legacy/converters/policy_to_line_item.py +++ b/calcrule_contribution_legacy/converters/policy_to_line_item.py @@ -27,7 +27,7 @@ def build_line_fk(cls, invoice_line_item, policy): @classmethod def build_dates(cls, invoice_line_item, policy): - invoice_line_item["date_valid_from"] = policy.effective_date + invoice_line_item["date_valid_from"] = policy.effective_date if policy.effective_date else policy.enroll_date invoice_line_item["date_valid_to"] = policy.expiry_date @classmethod @@ -59,7 +59,8 @@ def build_unit_price(cls, invoice_line_item, policy): @classmethod def build_discount(cls, invoice_line_item, policy): if policy.stage == Policy.STAGE_RENEWED: - invoice_line_item["discount"] = policy.product.renewal_discount_perc + invoice_line_item["discount"] = policy.product.renewal_discount_perc \ + if policy.product.renewal_discount_perc else 0 @classmethod def build_tax(cls, invoice_line_item): @@ -70,6 +71,7 @@ def build_tax(cls, invoice_line_item): def build_amounts(cls, invoice_line_item, policy): invoice_line_item["amount_net"] = invoice_line_item["quantity"] * invoice_line_item["unit_price"] if "discount" in invoice_line_item: - invoice_discount = invoice_line_item["amount_net"] * invoice_line_item["discount"] - invoice_line_item["amount_net"] = invoice_line_item["amount_net"] - invoice_discount + if invoice_line_item["discount"] > 0: + invoice_discount = invoice_line_item["amount_net"] * invoice_line_item["discount"] + invoice_line_item["amount_net"] = invoice_line_item["amount_net"] - invoice_discount invoice_line_item["amount_total"] = invoice_line_item["amount_net"] From 2ef822ad6d9e4602784a8f054496825ceaebb1e2 Mon Sep 17 00:00:00 2001 From: Patrick Delcroix Date: Mon, 2 May 2022 09:06:54 +0200 Subject: [PATCH 15/15] 1.4 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 46d9658..f5f7773 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ setup( name='openimis-be-calcrule-contribution-legacy', - version='1.0.0', + version='1.4.0', packages=find_packages(), include_package_data=True, license='GNU AGPL v3', @@ -40,4 +40,4 @@ 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', ], -) \ No newline at end of file +)