add links between items and tickets /tickets endpoint
This commit is contained in:
parent
d8be7f09e4
commit
2ece0cefd8
5 changed files with 81 additions and 7 deletions
|
@ -1,6 +1,6 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from tickets.models import IssueThread, Comment, StateChange, Assignment, ShippingVoucher
|
from tickets.models import IssueThread, Comment, StateChange, Assignment, ItemRelation, ShippingVoucher
|
||||||
|
|
||||||
|
|
||||||
class IssueThreadAdmin(admin.ModelAdmin):
|
class IssueThreadAdmin(admin.ModelAdmin):
|
||||||
|
@ -19,6 +19,10 @@ class AssignmentAdmin(admin.ModelAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ItemRelationAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ShippingVouchersAdmin(admin.ModelAdmin):
|
class ShippingVouchersAdmin(admin.ModelAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -27,4 +31,5 @@ admin.site.register(IssueThread, IssueThreadAdmin)
|
||||||
admin.site.register(Comment, CommentAdmin)
|
admin.site.register(Comment, CommentAdmin)
|
||||||
admin.site.register(StateChange, StateChangeAdmin)
|
admin.site.register(StateChange, StateChangeAdmin)
|
||||||
admin.site.register(Assignment, AssignmentAdmin)
|
admin.site.register(Assignment, AssignmentAdmin)
|
||||||
|
admin.site.register(ItemRelation, ItemRelationAdmin)
|
||||||
admin.site.register(ShippingVoucher, ShippingVouchersAdmin)
|
admin.site.register(ShippingVoucher, ShippingVouchersAdmin)
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Generated by Django 4.2.7 on 2024-06-23 02:17
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('inventory', '0004_alter_event_created_at_alter_item_created_at'),
|
||||||
|
('tickets', '0009_shippingvoucher'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='issuethread',
|
||||||
|
name='event',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='issue_threads', to='inventory.event'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ItemRelation',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||||
|
('timestamp', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('status', models.CharField(choices=[('possible', 'Possible'), ('confirmed', 'Confirmed'), ('discarded', 'Discarded'), ('provided', 'Provided')], default='possible', max_length=255)),
|
||||||
|
('issue_thread', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='item_relations', to='tickets.issuethread')),
|
||||||
|
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='issues', to='inventory.item')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='issuethread',
|
||||||
|
name='related_items',
|
||||||
|
field=models.ManyToManyField(through='tickets.ItemRelation', to='inventory.item'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -3,7 +3,7 @@ from django.utils import timezone
|
||||||
from django_softdelete.models import SoftDeleteModel
|
from django_softdelete.models import SoftDeleteModel
|
||||||
|
|
||||||
from authentication.models import ExtendedUser
|
from authentication.models import ExtendedUser
|
||||||
from inventory.models import Event
|
from inventory.models import Event, Item
|
||||||
from django.db.models.signals import post_save, pre_save
|
from django.db.models.signals import post_save, pre_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
|
||||||
|
@ -29,12 +29,21 @@ STATE_CHOICES = (
|
||||||
('found_closed', 'Item Found and stored externally and closed'),
|
('found_closed', 'Item Found and stored externally and closed'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
RELATION_STATUS_CHOICES = (
|
||||||
|
('possible', 'Possible'),
|
||||||
|
('confirmed', 'Confirmed'),
|
||||||
|
('discarded', 'Discarded'),
|
||||||
|
('provided', 'Provided'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class IssueThread(SoftDeleteModel):
|
class IssueThread(SoftDeleteModel):
|
||||||
id = models.AutoField(primary_key=True)
|
id = models.AutoField(primary_key=True)
|
||||||
uuid = models.CharField(max_length=255, unique=True, null=False, blank=False)
|
uuid = models.CharField(max_length=255, unique=True, null=False, blank=False)
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
|
event = models.ForeignKey(Event, null=True, on_delete=models.SET_NULL, related_name='issue_threads')
|
||||||
manually_created = models.BooleanField(default=False)
|
manually_created = models.BooleanField(default=False)
|
||||||
|
related_items = models.ManyToManyField(Item, through='ItemRelation')
|
||||||
|
|
||||||
def short_uuid(self):
|
def short_uuid(self):
|
||||||
return self.uuid[:8]
|
return self.uuid[:8]
|
||||||
|
@ -119,6 +128,17 @@ class Assignment(models.Model):
|
||||||
return str(self.issue_thread) + ' assigned to ' + self.assigned_to.username
|
return str(self.issue_thread) + ' assigned to ' + self.assigned_to.username
|
||||||
|
|
||||||
|
|
||||||
|
class ItemRelation(models.Model):
|
||||||
|
id = models.AutoField(primary_key=True)
|
||||||
|
issue_thread = models.ForeignKey(IssueThread, on_delete=models.CASCADE, related_name='item_relations')
|
||||||
|
item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='issues')
|
||||||
|
timestamp = models.DateTimeField(auto_now_add=True)
|
||||||
|
status = models.CharField(max_length=255, choices=RELATION_STATUS_CHOICES, default='possible')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.issue_thread) + ' related to ' + str(self.item)
|
||||||
|
|
||||||
|
|
||||||
class ShippingVoucher(models.Model):
|
class ShippingVoucher(models.Model):
|
||||||
id = models.AutoField(primary_key=True)
|
id = models.AutoField(primary_key=True)
|
||||||
issue_thread = models.ForeignKey(IssueThread, on_delete=models.CASCADE, related_name='shipping_vouchers', null=True)
|
issue_thread = models.ForeignKey(IssueThread, on_delete=models.CASCADE, related_name='shipping_vouchers', null=True)
|
||||||
|
|
|
@ -3,6 +3,7 @@ from rest_framework import serializers
|
||||||
from authentication.models import ExtendedUser
|
from authentication.models import ExtendedUser
|
||||||
from mail.api_v2 import AttachmentSerializer
|
from mail.api_v2 import AttachmentSerializer
|
||||||
from tickets.models import IssueThread, Comment, STATE_CHOICES, ShippingVoucher
|
from tickets.models import IssueThread, Comment, STATE_CHOICES, ShippingVoucher
|
||||||
|
from inventory.serializers import ItemSerializer
|
||||||
|
|
||||||
|
|
||||||
class CommentSerializer(serializers.ModelSerializer):
|
class CommentSerializer(serializers.ModelSerializer):
|
||||||
|
@ -40,11 +41,12 @@ class IssueSerializer(serializers.ModelSerializer):
|
||||||
last_activity = serializers.SerializerMethodField()
|
last_activity = serializers.SerializerMethodField()
|
||||||
assigned_to = serializers.SlugRelatedField(slug_field='username', queryset=ExtendedUser.objects.all(),
|
assigned_to = serializers.SlugRelatedField(slug_field='username', queryset=ExtendedUser.objects.all(),
|
||||||
allow_null=True, required=False)
|
allow_null=True, required=False)
|
||||||
|
related_items = ItemSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IssueThread
|
model = IssueThread
|
||||||
fields = ('id', 'timeline', 'name', 'state', 'assigned_to', 'last_activity', 'uuid')
|
fields = ('id', 'timeline', 'name', 'state', 'assigned_to', 'last_activity', 'uuid', 'related_items')
|
||||||
read_only_fields = ('id', 'timeline', 'last_activity', 'uuid')
|
read_only_fields = ('id', 'timeline', 'last_activity', 'uuid', 'related_items')
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
ret = super().to_internal_value(data)
|
ret = super().to_internal_value(data)
|
||||||
|
@ -69,7 +71,9 @@ class IssueSerializer(serializers.ModelSerializer):
|
||||||
last_mail = self.emails.order_by('-timestamp').first().timestamp if self.emails.count() > 0 else None
|
last_mail = self.emails.order_by('-timestamp').first().timestamp if self.emails.count() > 0 else None
|
||||||
last_assignment = self.assignments.order_by('-timestamp').first().timestamp if \
|
last_assignment = self.assignments.order_by('-timestamp').first().timestamp if \
|
||||||
self.assignments.count() > 0 else None
|
self.assignments.count() > 0 else None
|
||||||
args = [x for x in [last_state_change, last_comment, last_mail, last_assignment] if
|
last_relation = self.item_relations.order_by('-timestamp').first().timestamp if \
|
||||||
|
self.item_relations.count() > 0 else None
|
||||||
|
args = [x for x in [last_state_change, last_comment, last_mail, last_assignment, last_relation] if
|
||||||
x is not None]
|
x is not None]
|
||||||
return max(args)
|
return max(args)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -110,6 +114,14 @@ class IssueSerializer(serializers.ModelSerializer):
|
||||||
'timestamp': assignment.timestamp,
|
'timestamp': assignment.timestamp,
|
||||||
'assigned_to': assignment.assigned_to.username,
|
'assigned_to': assignment.assigned_to.username,
|
||||||
})
|
})
|
||||||
|
for relation in obj.item_relations.all():
|
||||||
|
timeline.append({
|
||||||
|
'type': 'item_relation',
|
||||||
|
'id': relation.id,
|
||||||
|
'status': relation.status,
|
||||||
|
'timestamp': relation.timestamp,
|
||||||
|
'item': ItemSerializer(relation.item).data,
|
||||||
|
})
|
||||||
for shipping_voucher in obj.shipping_vouchers.all():
|
for shipping_voucher in obj.shipping_vouchers.all():
|
||||||
timeline.append({
|
timeline.append({
|
||||||
'type': 'shipping_voucher',
|
'type': 'shipping_voucher',
|
||||||
|
|
|
@ -23,13 +23,15 @@
|
||||||
>
|
>
|
||||||
<template #actions="{ item }">
|
<template #actions="{ item }">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button class="btn btn-success" @click.stop="confirm('return Item?') && markItemReturned(item)" title="returned">
|
<button class="btn btn-success"
|
||||||
|
@click.stop="confirm('return Item?') && markItemReturned(item)" title="returned">
|
||||||
<font-awesome-icon icon="check"/>
|
<font-awesome-icon icon="check"/>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-secondary" @click.stop="openEditingModalWith(item)" title="edit">
|
<button class="btn btn-secondary" @click.stop="openEditingModalWith(item)" title="edit">
|
||||||
<font-awesome-icon icon="edit"/>
|
<font-awesome-icon icon="edit"/>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-danger" @click.stop="confirm('delete Item?') && deleteItem(item)" title="delete">
|
<button class="btn btn-danger" @click.stop="confirm('delete Item?') && deleteItem(item)"
|
||||||
|
title="delete">
|
||||||
<font-awesome-icon icon="trash"/>
|
<font-awesome-icon icon="trash"/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue