diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d19384..776343b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,14 +17,13 @@ on: jobs: call: name: Default CI Flow - uses: openimis/openimis-be_py/.github/workflows/ci_module.yml@develop + uses: openimis/openimis-be_py/.github/workflows/ci_module.yml@moldova secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} with: - SONAR_PROJECT_KEY: openimis_openimis-be-tasks_management_py + SONAR_PROJECT_KEY: openimis_openimis-be-worker_voucher_py SONAR_ORGANIZATION: openimis-1 - SONAR_PROJECT_NAME: openimis-be-tasks_management_py + SONAR_PROJECT_NAME: openimis-be-worker_voucher_py SONAR_PROJECT_VERSION: 1.0 - SONAR_SOURCES: tasks_management + SONAR_SOURCES: worker_voucher SONAR_EXCLUSIONS: "**/migrations/**,**/static/**,**/media/**,**/tests/**" - diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 6ff810f..4b00014 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -10,7 +10,7 @@ on: jobs: deploy: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: olegtarasov/get-tag@v2.1 @@ -24,7 +24,7 @@ jobs: 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" diff --git a/setup.py b/setup.py index 9754125..ec48b85 100644 --- a/setup.py +++ b/setup.py @@ -23,9 +23,10 @@ 'django', 'django-db-signals', 'djangorestframework', - 'openimis-be-core' - 'openimis-be-insuree' - '' + 'openimis-be-core', + 'openimis-be-insuree', + 'openimis-be-policyholder', + 'openimis-be-msystems' ], classifiers=[ 'Environment :: Web Environment', diff --git a/worker_voucher/apps.py b/worker_voucher/apps.py index c1c14e2..425829c 100644 --- a/worker_voucher/apps.py +++ b/worker_voucher/apps.py @@ -1,6 +1,33 @@ from django.apps import AppConfig +DEFAULT_CONFIG = { + "gql_worker_voucher_search_perms": ["204001"], + "gql_worker_voucher_create_perms": ["204002"], + "gql_worker_voucher_update_perms": ["204003"], + "gql_worker_voucher_delete_perms": ["204004"], +} + class WorkerVoucherConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'worker_voucher' + + gql_worker_voucher_search_perms = None + gql_worker_voucher_create_perms = None + gql_worker_voucher_update_perms = None + gql_worker_voucher_delete_perms = None + + def ready(self): + from core.models import ModuleConfiguration + + cfg = ModuleConfiguration.get_or_default(self.name, DEFAULT_CONFIG) + self._load_config(cfg) + + @classmethod + def _load_config(cls, cfg): + """ + Load all config fields that match current AppConfig class fields, all custom fields have to be loaded separately + """ + for field in cfg: + if hasattr(cls, field): + setattr(cls, field, cfg[field]) diff --git a/worker_voucher/gql_queries.py b/worker_voucher/gql_queries.py new file mode 100644 index 0000000..08207ad --- /dev/null +++ b/worker_voucher/gql_queries.py @@ -0,0 +1,32 @@ +import graphene + +from graphene_django import DjangoObjectType +from core import ExtendedConnection, prefix_filterset +from insuree.gql_queries import InsureeGQLType +from policyholder.gql import PolicyHolderGQLType +from worker_voucher.models import WorkerVoucher + + +class WorkerVoucherGQLType(DjangoObjectType): + uuid = graphene.String(source='uuid') + + class Meta: + model = WorkerVoucher + interfaces = (graphene.relay.Node,) + filter_fields = { + "id": ["exact"], + + "code": ["exact", "iexact", "istartswith", "icontains"], + "status": ["exact", "iexact", "istartswith", "icontains"], + "assigned_date": ["exact", "lt", "lte", "gt", "gte"], + "expiry_date": ["exact", "lt", "lte", "gt", "gte"], + + **prefix_filterset("insuree__", InsureeGQLType._meta.filter_fields), + **prefix_filterset("policyholder__", PolicyHolderGQLType._meta.filter_fields), + + "date_created": ["exact", "lt", "lte", "gt", "gte"], + "date_updated": ["exact", "lt", "lte", "gt", "gte"], + "is_deleted": ["exact"], + "version": ["exact"], + } + connection_class = ExtendedConnection diff --git a/worker_voucher/migrations/0001_initial.py b/worker_voucher/migrations/0001_initial.py new file mode 100644 index 0000000..284a836 --- /dev/null +++ b/worker_voucher/migrations/0001_initial.py @@ -0,0 +1,78 @@ +# Generated by Django 3.2.22 on 2023-11-24 08:36 + +import core.fields +import dirtyfields.dirtyfields +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import simple_history.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('policyholder', '0017_auto_20230126_0903'), + ('insuree', '0019_auto_20231026_1205'), + ('core', '0025_mutationlog_json_ext'), + ('msystems', '0005_fix_ward'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='WorkerVoucher', + fields=[ + ('id', models.UUIDField(db_column='UUID', default=None, editable=False, primary_key=True, serialize=False)), + ('is_deleted', models.BooleanField(db_column='isDeleted', default=False)), + ('json_ext', models.JSONField(blank=True, db_column='Json_ext', null=True)), + ('date_created', core.fields.DateTimeField(db_column='DateCreated', null=True)), + ('date_updated', core.fields.DateTimeField(db_column='DateUpdated', null=True)), + ('version', models.IntegerField(default=1)), + ('code', models.CharField(blank=True, max_length=255, null=True)), + ('status', models.CharField(blank=True, choices=[('AWAITING_PAYMENT', 'Awaiting Payment'), ('UNASSIGNED', 'Unassigned'), ('ASSIGNED', 'Assigned'), ('EXPIRED', 'Expired'), ('CANCELED', 'Canceled'), ('CLOSED', 'Closed')], default='AWAITING_PAYMENT', max_length=255, null=True)), + ('assigned_date', core.fields.DateField(blank=True, null=True)), + ('expiry_date', core.fields.DateField(blank=True, null=True)), + ('insuree', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='insuree.insuree')), + ('policyholder', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='policyholder.policyholder')), + ('user_created', models.ForeignKey(db_column='UserCreatedUUID', on_delete=django.db.models.deletion.DO_NOTHING, related_name='workervoucher_user_created', to=settings.AUTH_USER_MODEL)), + ('user_updated', models.ForeignKey(db_column='UserUpdatedUUID', on_delete=django.db.models.deletion.DO_NOTHING, related_name='workervoucher_user_updated', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + bases=(dirtyfields.dirtyfields.DirtyFieldsMixin, models.Model), + ), + migrations.CreateModel( + name='HistoricalWorkerVoucher', + fields=[ + ('id', models.UUIDField(db_column='UUID', db_index=True, default=None, editable=False)), + ('is_deleted', models.BooleanField(db_column='isDeleted', default=False)), + ('json_ext', models.JSONField(blank=True, db_column='Json_ext', null=True)), + ('date_created', core.fields.DateTimeField(db_column='DateCreated', null=True)), + ('date_updated', core.fields.DateTimeField(db_column='DateUpdated', null=True)), + ('version', models.IntegerField(default=1)), + ('code', models.CharField(blank=True, max_length=255, null=True)), + ('status', models.CharField(blank=True, choices=[('AWAITING_PAYMENT', 'Awaiting Payment'), ('UNASSIGNED', 'Unassigned'), ('ASSIGNED', 'Assigned'), ('EXPIRED', 'Expired'), ('CANCELED', 'Canceled'), ('CLOSED', 'Closed')], default='AWAITING_PAYMENT', max_length=255, null=True)), + ('assigned_date', core.fields.DateField(blank=True, null=True)), + ('expiry_date', core.fields.DateField(blank=True, null=True)), + ('history_id', models.AutoField(primary_key=True, serialize=False)), + ('history_date', models.DateTimeField(db_index=True)), + ('history_change_reason', models.CharField(max_length=100, null=True)), + ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ('insuree', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='insuree.insuree')), + ('policyholder', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='policyholder.policyholder')), + ('user_created', models.ForeignKey(blank=True, db_column='UserCreatedUUID', db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL)), + ('user_updated', models.ForeignKey(blank=True, db_column='UserUpdatedUUID', db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'historical worker voucher', + 'verbose_name_plural': 'historical worker vouchers', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': ('history_date', 'history_id'), + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + ] diff --git a/worker_voucher/migrations/0002_voucher_rights.py b/worker_voucher/migrations/0002_voucher_rights.py new file mode 100644 index 0000000..a2e7a2f --- /dev/null +++ b/worker_voucher/migrations/0002_voucher_rights.py @@ -0,0 +1,46 @@ +from django.db import migrations + +rights = ['204001', '204002', '204003', '204001'] +roles = ['Employer', 'Inspector', 'IMIS Administrator'] + + +def add_rights(role_name, role_model, role_right_model): + role = role_model.objects.get(name=role_name) + for right_id in rights: + if not role_right_model.objects.filter(validity_to__isnull=True, role=role, right_id=right_id).exists(): + _add_right_for_role(role, right_id, role_right_model) + + +def _add_right_for_role(role, right_id, role_right_model): + role_right_model.objects.create(role=role, right_id=right_id, audit_user_id=1) + + +def remove_rights(role_id, role_right_model): + role_right_model.objects.filter( + role__is_system=role_id, + right_id__in=rights, + validity_to__isnull=True + ).delete() + + +def on_migration(apps, schema_editor): + role_model = apps.get_model("core", "role") + role_right_model = apps.get_model("core", "roleright") + for role in roles: + add_rights(role, role_model, role_right_model) + + +def on_reverse_migration(apps, schema_editor): + role_right_model = apps.get_model("core", "roleright") + for role in roles: + remove_rights(role, role_right_model) + + +class Migration(migrations.Migration): + dependencies = [ + ('worker_voucher', '0001_initial'), + ] + + operations = [ + migrations.RunPython(on_migration, on_reverse_migration), + ] diff --git a/worker_voucher/models.py b/worker_voucher/models.py index 71a8362..d963618 100644 --- a/worker_voucher/models.py +++ b/worker_voucher/models.py @@ -1,3 +1,24 @@ from django.db import models +from django.utils.translation import gettext_lazy as _ -# Create your models here. +from core.models import HistoryModel +from core import fields +from insuree.models import Insuree +from policyholder.models import PolicyHolder + + +class WorkerVoucher(HistoryModel): + class Status(models.TextChoices): + PENDING = 'AWAITING_PAYMENT', _('Awaiting Payment') + UNASSIGNED = 'UNASSIGNED', _('Unassigned') + ASSIGNED = 'ASSIGNED', _('Assigned') + EXPIRED = 'EXPIRED', _('Expired') + CANCELED = 'CANCELED', _('Canceled') + CLOSED = 'CLOSED', _('Closed') + + insuree = models.ForeignKey(Insuree, null=True, blank=True, on_delete=models.DO_NOTHING) + policyholder = models.ForeignKey(PolicyHolder, null=True, blank=True, on_delete=models.DO_NOTHING) + code = models.CharField(max_length=255, blank=True, null=True) + status = models.CharField(max_length=255, blank=True, null=True, choices=Status.choices, default=Status.PENDING) + assigned_date = fields.DateField(blank=True, null=True) + expiry_date = fields.DateField(blank=True, null=True) diff --git a/worker_voucher/schema.py b/worker_voucher/schema.py new file mode 100644 index 0000000..c0d96dc --- /dev/null +++ b/worker_voucher/schema.py @@ -0,0 +1,36 @@ +import graphene +import graphene_django_optimizer as gql_optimizer + +from django.db.models import Q +from django.contrib.auth.models import AnonymousUser +from core.schema import OrderedDjangoFilterConnectionField +from core.utils import append_validity_filter +from worker_voucher.apps import WorkerVoucherConfig +from worker_voucher.gql_queries import WorkerVoucherGQLType +from worker_voucher.models import WorkerVoucher + + +class Query(graphene.ObjectType): + module_name = "tasks_management" + + worker_voucher = OrderedDjangoFilterConnectionField( + WorkerVoucherGQLType, + orderBy=graphene.List(of_type=graphene.String), + client_mutation_id=graphene.String(), + ) + + def resolve_worker_voucher(self, info, **kwargs): + Query._check_permissions(info.context.user, WorkerVoucherConfig.gql_worker_voucher_search_perms) + filters = append_validity_filter(**kwargs) + + client_mutation_id = kwargs.get("client_mutation_id", None) + if client_mutation_id: + filters.append(Q(mutations__mutation__client_mutation_id=client_mutation_id)) + + query = WorkerVoucher.objects.filter(*filters) + return gql_optimizer.query(query, info) + + @staticmethod + def _check_permissions(user, perms): + if type(user) is AnonymousUser or not user.id or not user.has_perms(perms): + raise PermissionError("Unauthorized") diff --git a/worker_voucher/services.py b/worker_voucher/services.py index 7aa153f..5534955 100644 --- a/worker_voucher/services.py +++ b/worker_voucher/services.py @@ -1,16 +1,27 @@ import logging -import warnings + +from core.services import BaseService +from core.signals import register_service_signal +from worker_voucher.models import WorkerVoucher +from worker_voucher.validation import WorkerVoucherValidation logger = logging.getLogger(__name__) -# Remove this code when implementing services -warnings.warn("The example code in service is still present.") +class WorkerVoucherService(BaseService): + OBJECT_TYPE = WorkerVoucher + + def __init__(self, user, validation_class=WorkerVoucherValidation): + super().__init__(user, validation_class) -def example_service_function_job(): - pass + @register_service_signal('worker_voucher_service.create') + def create(self, obj_data): + return super().create(obj_data) + @register_service_signal('worker_voucher_service.update') + def update(self, obj_data): + return super().update(obj_data) -class ExampleService: - def example_service_method_job(self): - pass + @register_service_signal('worker_voucher_service.delete') + def delete(self, obj_data): + return super().delete(obj_data) diff --git a/worker_voucher/tests.py b/worker_voucher/tests.py index b3f87b2..e69de29 100644 --- a/worker_voucher/tests.py +++ b/worker_voucher/tests.py @@ -1,17 +0,0 @@ -import warnings - -from django.test import TestCase -from worker_voucher.services import example_service_function_job, ExampleService - - -class ExampleImisTest(TestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - # Remove this code when implementing tests - warnings.warn("The example code in test case is still present.") - - def test_example_module_loaded_correctly(self): - example_service_function_job() - ExampleService().example_service_method_job() - self.assertTrue(True) diff --git a/worker_voucher/validation.py b/worker_voucher/validation.py new file mode 100644 index 0000000..c478205 --- /dev/null +++ b/worker_voucher/validation.py @@ -0,0 +1,6 @@ +from core.validation import BaseModelValidation +from worker_voucher.models import WorkerVoucher + + +class WorkerVoucherValidation(BaseModelValidation): + OBJECT_TYPE = WorkerVoucher