diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml new file mode 100644 index 0000000..15a80ea --- /dev/null +++ b/.github/workflows/django.yml @@ -0,0 +1,98 @@ +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: | + 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 diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..49f5ca4 --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,99 @@ +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: | + 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 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..21a600d 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 cls.__module__.split('.')[1] == 'calculation_rule': + 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..16fd4eb --- /dev/null +++ b/calcrule_contribution_legacy/calculation_rule.py @@ -0,0 +1,177 @@ +from django.contrib.contenttypes.models import ContentType +from django.db.models.query import Q + +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 policy.models import Policy + + +class ContributionPlanCalculationRuleProductModeling(AbsCalculationRule): + version = 1 + uuid = "2aee6d54-eef4-4ee6-1c47-2793cfa5f9a8" + calculation_rule_name = "CV: legacy" + 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 + type = "account_receivable" + sub_type = "contribution" + + 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__ 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 == "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": + 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 + 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, **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__ + 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): + 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 + @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 == "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 + + @classmethod + 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], + 'type_conversion': 'policy-invoice' + } + + @classmethod + def _convert_contract(cls, instance, **kwargs): + 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/config.py b/calcrule_contribution_legacy/config.py new file mode 100644 index 0000000..36b215f --- /dev/null +++ b/calcrule_contribution_legacy/config.py @@ -0,0 +1,12 @@ +CLASS_RULE_PARAM_VALIDATION = [] + +FROM_TO = [ + {"from": "Policy", "to": "Invoice"}, + {"from": "ContractContributionPlanDetails", "to": "InvoiceLine"} +] + +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" diff --git a/calcrule_contribution_legacy/converters/__init__.py b/calcrule_contribution_legacy/converters/__init__.py new file mode 100644 index 0000000..ddae1cc --- /dev/null +++ b/calcrule_contribution_legacy/converters/__init__.py @@ -0,0 +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_invoice.py b/calcrule_contribution_legacy/converters/policy_to_invoice.py new file mode 100644 index 0000000..b60d8cc --- /dev/null +++ b/calcrule_contribution_legacy/converters/policy_to_invoice.py @@ -0,0 +1,61 @@ +from django.contrib.contenttypes.models import ContentType +from invoice.apps import InvoiceConfig +from invoice.models import Invoice + + +class PolicyToInvoiceConverter(object): + + @classmethod + def to_invoice_obj(cls, policy): + 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) + return invoice + + @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 if policy.effective_date else policy.enroll_date + invoice["date_invoice"] = policy.enroll_date + invoice["date_valid_from"] = policy.effective_date if policy.effective_date else policy.enroll_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..0218cec --- /dev/null +++ b/calcrule_contribution_legacy/converters/policy_to_line_item.py @@ -0,0 +1,77 @@ +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_line_item = {} + 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) + return invoice_line_item + + @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 if policy.effective_date else policy.enroll_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.product.renewal_discount_perc \ + if policy.product.renewal_discount_perc else 0 + + @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: + 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"] 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 diff --git a/setup.py b/setup.py index 7aa8d63..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', @@ -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', @@ -37,4 +40,4 @@ 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', ], -) \ No newline at end of file +)