Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[ISSUE-3694] add contract exclusion set sync and constraints for work types on tickets and tasks #224

2 changes: 1 addition & 1 deletion djautotask/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
VERSION = (1, 6, 8, 'final')
VERSION = (1, 6, 9, 'final')

# pragma: no cover
if VERSION[-1] != "final":
Expand Down
18 changes: 17 additions & 1 deletion djautotask/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,8 @@ class ResourceServiceDeskRoleAdmin(admin.ModelAdmin):

@admin.register(models.Contract)
class ContractAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'number', 'account', 'status_name')
list_display = ('id', 'name', 'number', 'account', 'status_name',
'contract_exclusion_set_id')
search_fields = ('id', 'name', 'number')
list_filter = ('status',)

Expand Down Expand Up @@ -402,3 +403,18 @@ class ProjectNoteTypeAdmin(admin.ModelAdmin):
@admin.register(models.CompanyAlert)
class CompanyAlertsAdmin(admin.ModelAdmin):
list_display = ('id', 'alert_text', 'alert_type', 'account')


@admin.register(models.ContractExclusionSet)
class ContractExclusionSetAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'is_active')


@admin.register(models.ContractExclusionSetExcludedWorkType)
class ContractExcludedWorkTypeAdmin(admin.ModelAdmin):
list_display = ('id', 'contract_exclusion_set', 'excluded_work_type')


@admin.register(models.ContractExclusionSetExcludedRole)
class ContractExcludedRoleAdmin(admin.ModelAdmin):
list_display = ('id', 'contract_exclusion_set', 'excluded_role')
12 changes: 12 additions & 0 deletions djautotask/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,18 @@ class ContractsAPIClient(AutotaskAPIClient):
API = 'Contracts'


class ContractExclusionSetAPIClient(AutotaskAPIClient):
API = 'ContractExclusionSets'


class ContractsExcludedWorkTypeAPIClient(AutotaskAPIClient):
API = 'ContractExclusionSetExcludedWorkTypes'


class ContractsExcludedRoleAPIClient(AutotaskAPIClient):
API = 'ContractExclusionSetExcludedRoles'


class AccountPhysicalLocationsAPIClient(AutotaskAPIClient):
API = 'CompanyLocations'

Expand Down
15 changes: 15 additions & 0 deletions djautotask/management/commands/atsync.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,21 @@ def __init__(self, *args, **kwargs):
('task_predecessor', sync.TaskPredecessorSynchronizer,
_('Task Predecessor')),
('contact', sync.ContactSynchronizer, _('Contact')),
(
'contract_exclusion_set',
sync.ContractExclusionSetSynchronizer,
_('Contract Exclusion Set')
),
(
'contract_excluded_work_type',
sync.ContractExcludedWorkTypeSynchronizer,
_('Contract Excluded Work Type')
),
(
'contract_excluded_role',
sync.ContractExcludedRoleSynchronizer,
_('Contract Excluded Role')
),
)
self.synchronizer_map = OrderedDict()
for name, synchronizer, obj_name in synchronizers:
Expand Down
90 changes: 90 additions & 0 deletions djautotask/migrations/0118_contractexclusionset_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Generated by Django 4.2.16 on 2024-10-30 23:14

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('djautotask', '0117_alter_tasknote_task_alter_ticketnote_ticket'),
]

operations = [
migrations.CreateModel(
name='ContractExclusionSet',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('description', models.TextField(blank=True, null=True)),
('is_active', models.BooleanField(default=True)),
('name', models.CharField(max_length=255)),
],
),
migrations.AddField(
model_name='contract',
name='contract_exclusion_set_id',
field=models.IntegerField(blank=True, null=True),
),
migrations.CreateModel(
name='ContractExclusionSetExcludedWorkType',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('contract_exclusion_set', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='djautotask.contractexclusionset')),
('excluded_work_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='djautotask.billingcode')),
],
),
migrations.CreateModel(
name='ContractExclusionSetExcludedRole',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('contract_exclusion_set', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='djautotask.contractexclusionset')),
('excluded_role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='djautotask.role')),
],
),
migrations.AddField(
model_name='contractexclusionset',
name='excluded_roles',
field=models.ManyToManyField(related_name='excluded_role_sets', through='djautotask.ContractExclusionSetExcludedRole', to='djautotask.role'),
),
migrations.AddField(
model_name='contractexclusionset',
name='excluded_work_types',
field=models.ManyToManyField(related_name='excluded_work_type_sets', through='djautotask.ContractExclusionSetExcludedWorkType', to='djautotask.billingcode'),
),
migrations.CreateModel(
name='ContractExcludedRoleTracker',
fields=[
],
options={
'db_table': 'djautotask_contractexclusionsetrole',
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('djautotask.contractexclusionsetexcludedrole',),
),
migrations.CreateModel(
name='ContractExcludedWorkTypeTracker',
fields=[
],
options={
'db_table': 'djautotask_contractexclusionsetworktype',
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('djautotask.contractexclusionsetexcludedworktype',),
),
migrations.CreateModel(
name='ContractExclusionSetTracker',
fields=[
],
options={
'db_table': 'djautotask_contractexclusionset',
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('djautotask.contractexclusionset',),
),
]
14 changes: 14 additions & 0 deletions djautotask/migrations/0120_merge_20241101_1316.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Generated by Django 4.2.16 on 2024-11-01 13:16

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('djautotask', '0118_contractexclusionset_and_more'),
('djautotask', '0119_merge_20241024_1521'),
]

operations = [
]
64 changes: 63 additions & 1 deletion djautotask/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -948,10 +948,12 @@ class Contract(models.Model):
number = models.CharField(blank=True, null=True, max_length=50)
status = models.CharField(
max_length=20, blank=True, null=True, choices=STATUS_CHOICES)

account = models.ForeignKey(
'Account', blank=True, null=True, on_delete=models.SET_NULL
)
contract_exclusion_set_id = models.IntegerField(
blank=True, null=True
)

class Meta:
ordering = ('name',)
Expand All @@ -960,6 +962,42 @@ def __str__(self):
return self.name


class ContractExclusionSet(models.Model):
description = models.TextField(blank=True, null=True)
is_active = models.BooleanField(default=True)
name = models.CharField(max_length=255)
excluded_work_types = models.ManyToManyField(
'BillingCode', through='ContractExclusionSetExcludedWorkType',
related_name='excluded_work_type_sets'
)
excluded_roles = models.ManyToManyField(
'Role', through='ContractExclusionSetExcludedRole',
related_name='excluded_role_sets'
)

def __str__(self):
return self.name


class ContractExclusionSetExcludedWorkType(models.Model):
contract_exclusion_set = models.ForeignKey('ContractExclusionSet',
on_delete=models.CASCADE)
excluded_work_type = models.ForeignKey('BillingCode',
on_delete=models.CASCADE)

def __str__(self):
return str(self.id) or ''


class ContractExclusionSetExcludedRole(models.Model):
contract_exclusion_set = models.ForeignKey('ContractExclusionSet',
on_delete=models.CASCADE)
excluded_role = models.ForeignKey('Role', on_delete=models.CASCADE)

def __str__(self):
return str(self.id) or ''


class ServiceCall(TimeStampedModel):
description = models.TextField(blank=True, null=True, max_length=2000)
duration = models.DecimalField(
Expand Down Expand Up @@ -1463,6 +1501,30 @@ class Meta:
db_table = 'djautotask_contract'


class ContractExclusionSetTracker(ContractExclusionSet):
tracker = FieldTracker()

class Meta:
proxy = True
db_table = 'djautotask_contractexclusionset'


class ContractExcludedWorkTypeTracker(ContractExclusionSetExcludedWorkType):
tracker = FieldTracker()

class Meta:
proxy = True
db_table = 'djautotask_contractexclusionsetworktype'


class ContractExcludedRoleTracker(ContractExclusionSetExcludedRole):
tracker = FieldTracker()

class Meta:
proxy = True
db_table = 'djautotask_contractexclusionsetrole'


class ServiceCallTracker(ServiceCall):
tracker = FieldTracker()

Expand Down
72 changes: 72 additions & 0 deletions djautotask/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -1812,6 +1812,8 @@ def _assign_field_data(self, instance, object_data):
instance.name = object_data.get('contractName')
instance.number = object_data.get('contractNumber')
instance.status = str(object_data.get('status'))
instance.contract_exclusion_set_id = \
object_data.get('contractExclusionSetID')

self.set_relations(instance, object_data)

Expand Down Expand Up @@ -2003,6 +2005,76 @@ def active_ids(self):
return active_ids


class ContractExclusionSetSynchronizer(Synchronizer):
client_class = api.ContractExclusionSetAPIClient
model_class = models.ContractExclusionSetTracker
last_updated_field = None

def _assign_field_data(self, instance, object_data):
instance.id = object_data['id']
instance.name = object_data['name']
instance.is_active = object_data['isActive']
instance.description = object_data['description']

return instance


class ContractExcludedWorkTypeSynchronizer(BatchQueryMixin, Synchronizer):
client_class = api.ContractsExcludedWorkTypeAPIClient
model_class = models.ContractExcludedWorkTypeTracker
condition_field_name = 'contractExclusionSetID'
last_updated_field = None

related_meta = {
'contractExclusionSetID':
(models.ContractExclusionSet, 'contract_exclusion_set'),
'excludedWorkTypeID': (models.BillingCode, 'excluded_work_type')
}

def _assign_field_data(self, instance, object_data):
instance.id = object_data['id']
self.set_relations(instance, object_data)

return instance

@property
def active_ids(self):
active_ids = models.ContractExclusionSet.objects.exclude(
is_active=False
).values_list(
'id', flat=True
)
return active_ids


class ContractExcludedRoleSynchronizer(BatchQueryMixin, Synchronizer):
client_class = api.ContractsExcludedRoleAPIClient
model_class = models.ContractExcludedRoleTracker
condition_field_name = 'contractExclusionSetID'
last_updated_field = None

related_meta = {
'contractExclusionSetID':
(models.ContractExclusionSet, 'contract_exclusion_set'),
'excludedRoleID': (models.Role, 'excluded_role')
}

def _assign_field_data(self, instance, object_data):
instance.id = object_data['id']
self.set_relations(instance, object_data)

return instance

@property
def active_ids(self):
active_ids = models.ContractExclusionSet.objects.exclude(
is_active=False
).values_list(
'id', flat=True
)
return active_ids


class NoteTypeSynchronizer(PicklistSynchronizer):
# Ticket note types are including task note types, and there are other
# note types currently not used. e.g. project note types
Expand Down
25 changes: 25 additions & 0 deletions djautotask/tests/fixture_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,31 @@ def init_contracts():
return synchronizer.sync()


def init_contract_exclusion_sets():
models.ContractExclusionSet.objects.all().delete()
mocks.service_api_get_contract_exclusion_sets_call(
fixtures.API_CONTRACT_EXCLUSION_SET)
synchronizer = sync.ContractExclusionSetSynchronizer()
return synchronizer.sync()


def init_contract_exclusion_roles():
models.ContractExclusionSetExcludedRole.objects.all() \
.delete()
mocks.service_api_get_contract_excluded_roles_call(
fixtures.API_CONTRACT_EXCLUSION_ROLE)
synchronizer = sync.ContractExcludedRoleSynchronizer()
return synchronizer.sync()


def init_contract_exclusion_work_types():
models.ContractExclusionSetExcludedWorkType.objects.all().delete()
mocks.service_api_get_contract_excluded_work_types_call(
fixtures.API_CONTRACT_EXCLUSION_WORK_TYPE)
synchronizer = sync.ContractExcludedWorkTypeSynchronizer()
return synchronizer.sync()


def init_service_calls():
models.ServiceCall.objects.all().delete()
mocks.service_api_get_service_calls_call(fixtures.API_SERVICE_CALL)
Expand Down
Loading
Loading