diff --git a/core/tickets/admin.py b/core/tickets/admin.py index 20cb83f..d842482 100644 --- a/core/tickets/admin.py +++ b/core/tickets/admin.py @@ -1,6 +1,6 @@ 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): @@ -19,6 +19,10 @@ class AssignmentAdmin(admin.ModelAdmin): pass +class ItemRelationAdmin(admin.ModelAdmin): + pass + + class ShippingVouchersAdmin(admin.ModelAdmin): pass @@ -27,4 +31,5 @@ admin.site.register(IssueThread, IssueThreadAdmin) admin.site.register(Comment, CommentAdmin) admin.site.register(StateChange, StateChangeAdmin) admin.site.register(Assignment, AssignmentAdmin) +admin.site.register(ItemRelation, ItemRelationAdmin) admin.site.register(ShippingVoucher, ShippingVouchersAdmin) diff --git a/core/tickets/migrations/0010_issuethread_event_itemrelation_and_more.py b/core/tickets/migrations/0010_issuethread_event_itemrelation_and_more.py new file mode 100644 index 0000000..06ec4fd --- /dev/null +++ b/core/tickets/migrations/0010_issuethread_event_itemrelation_and_more.py @@ -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'), + ), + ] diff --git a/core/tickets/models.py b/core/tickets/models.py index 141cf27..db427fe 100644 --- a/core/tickets/models.py +++ b/core/tickets/models.py @@ -3,7 +3,7 @@ from django.utils import timezone from django_softdelete.models import SoftDeleteModel 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.dispatch import receiver @@ -29,12 +29,21 @@ STATE_CHOICES = ( ('found_closed', 'Item Found and stored externally and closed'), ) +RELATION_STATUS_CHOICES = ( + ('possible', 'Possible'), + ('confirmed', 'Confirmed'), + ('discarded', 'Discarded'), + ('provided', 'Provided'), +) + class IssueThread(SoftDeleteModel): id = models.AutoField(primary_key=True) uuid = models.CharField(max_length=255, unique=True, null=False, blank=False) 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) + related_items = models.ManyToManyField(Item, through='ItemRelation') def short_uuid(self): return self.uuid[:8] @@ -119,6 +128,17 @@ class Assignment(models.Model): 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): id = models.AutoField(primary_key=True) issue_thread = models.ForeignKey(IssueThread, on_delete=models.CASCADE, related_name='shipping_vouchers', null=True) diff --git a/core/tickets/serializers.py b/core/tickets/serializers.py index 448a902..a980b97 100644 --- a/core/tickets/serializers.py +++ b/core/tickets/serializers.py @@ -3,6 +3,7 @@ from rest_framework import serializers from authentication.models import ExtendedUser from mail.api_v2 import AttachmentSerializer from tickets.models import IssueThread, Comment, STATE_CHOICES, ShippingVoucher +from inventory.serializers import ItemSerializer class CommentSerializer(serializers.ModelSerializer): @@ -40,11 +41,12 @@ class IssueSerializer(serializers.ModelSerializer): last_activity = serializers.SerializerMethodField() assigned_to = serializers.SlugRelatedField(slug_field='username', queryset=ExtendedUser.objects.all(), allow_null=True, required=False) + related_items = ItemSerializer(many=True, read_only=True) class Meta: model = IssueThread - fields = ('id', 'timeline', 'name', 'state', 'assigned_to', 'last_activity', 'uuid') - read_only_fields = ('id', 'timeline', 'last_activity', 'uuid') + fields = ('id', 'timeline', 'name', 'state', 'assigned_to', 'last_activity', 'uuid', 'related_items') + read_only_fields = ('id', 'timeline', 'last_activity', 'uuid', 'related_items') def to_internal_value(self, 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_assignment = self.assignments.order_by('-timestamp').first().timestamp if \ 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] return max(args) except AttributeError: @@ -110,6 +114,14 @@ class IssueSerializer(serializers.ModelSerializer): 'timestamp': assignment.timestamp, '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(): timeline.append({ 'type': 'shipping_voucher', diff --git a/web/src/views/Items.vue b/web/src/views/Items.vue index 5824999..9d127f0 100644 --- a/web/src/views/Items.vue +++ b/web/src/views/Items.vue @@ -23,13 +23,15 @@ >