add links between items and tickets /tickets endpoint

This commit is contained in:
j3d1 2024-06-23 04:19:54 +02:00
parent d8be7f09e4
commit 2ece0cefd8
5 changed files with 81 additions and 7 deletions

View file

@ -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)

View file

@ -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'),
),
]

View file

@ -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)

View file

@ -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',

View file

@ -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>