make tickets assignable to users #48
11 changed files with 317 additions and 110 deletions
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 4.2.7 on 2024-01-22 16:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0003_alter_item_options'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='created_at',
|
||||
field=models.DateTimeField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='item',
|
||||
name='created_at',
|
||||
field=models.DateTimeField(auto_now_add=True, null=True),
|
||||
),
|
||||
]
|
|
@ -8,7 +8,6 @@ class ItemManager(SoftDeleteManager):
|
|||
def create(self, **kwargs):
|
||||
if 'uid' in kwargs:
|
||||
raise ValueError('uid must not be set manually')
|
||||
#uid = Item.objects.filter(event=kwargs['event']).count() + 1
|
||||
uid = Item.all_objects.filter(event=kwargs['event']).count() + 1
|
||||
kwargs['uid'] = uid
|
||||
return super().create(**kwargs)
|
||||
|
@ -24,7 +23,7 @@ class Item(SoftDeleteModel):
|
|||
event = models.ForeignKey('Event', models.CASCADE, db_column='eid')
|
||||
container = models.ForeignKey('Container', models.CASCADE, db_column='cid')
|
||||
returned_at = models.DateTimeField(blank=True, null=True)
|
||||
created_at = models.DateTimeField(blank=True, null=True)
|
||||
created_at = models.DateTimeField(null=True, auto_now_add=True)
|
||||
updated_at = models.DateTimeField(blank=True, null=True)
|
||||
|
||||
objects = ItemManager()
|
||||
|
@ -36,6 +35,7 @@ class Item(SoftDeleteModel):
|
|||
('match_item', 'Can match item')
|
||||
]
|
||||
|
||||
|
||||
class Container(SoftDeleteModel):
|
||||
cid = models.AutoField(primary_key=True)
|
||||
name = models.CharField(max_length=255)
|
||||
|
@ -51,5 +51,5 @@ class Event(models.Model):
|
|||
end = models.DateTimeField(blank=True, null=True)
|
||||
pre_start = models.DateTimeField(blank=True, null=True)
|
||||
post_end = models.DateTimeField(blank=True, null=True)
|
||||
created_at = models.DateTimeField(blank=True, null=True)
|
||||
created_at = models.DateTimeField(null=True, auto_now_add=True)
|
||||
updated_at = models.DateTimeField(blank=True, null=True)
|
||||
|
|
|
@ -2,7 +2,7 @@ import logging
|
|||
|
||||
from django.urls import re_path
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from rest_framework import routers, viewsets, serializers, status
|
||||
from rest_framework import routers, viewsets, status
|
||||
from rest_framework.decorators import api_view, permission_classes
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
|
@ -10,78 +10,11 @@ from asgiref.sync import async_to_sync
|
|||
from channels.layers import get_channel_layer
|
||||
|
||||
from core.settings import MAIL_DOMAIN
|
||||
from mail.api_v2 import AttachmentSerializer
|
||||
from mail.models import Email
|
||||
from mail.protocol import send_smtp, make_reply, collect_references
|
||||
from notify_sessions.models import SystemEvent
|
||||
from tickets.models import IssueThread, Comment, STATE_CHOICES
|
||||
|
||||
|
||||
class IssueSerializer(serializers.ModelSerializer):
|
||||
timeline = serializers.SerializerMethodField()
|
||||
last_activity = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = IssueThread
|
||||
fields = ('id', 'timeline', 'name', 'state', 'assigned_to', 'last_activity', 'uuid')
|
||||
read_only_fields = ('id', 'timeline', 'last_activity', 'uuid')
|
||||
|
||||
def to_internal_value(self, data):
|
||||
ret = super().to_internal_value(data)
|
||||
if 'state' in data:
|
||||
ret['state'] = data['state']
|
||||
return ret
|
||||
|
||||
def validate(self, attrs):
|
||||
if 'state' in attrs:
|
||||
if attrs['state'] not in [x[0] for x in STATE_CHOICES]:
|
||||
raise serializers.ValidationError('invalid state')
|
||||
return attrs
|
||||
|
||||
@staticmethod
|
||||
def get_last_activity(self):
|
||||
try:
|
||||
last_state_change = self.state_changes.order_by('-timestamp').first().timestamp \
|
||||
if self.state_changes.count() > 0 else None
|
||||
last_comment = self.comments.order_by('-timestamp').first().timestamp if self.comments.count() > 0 else None
|
||||
last_mail = self.emails.order_by('-timestamp').first().timestamp if self.emails.count() > 0 else None
|
||||
args = [x for x in [last_state_change, last_comment, last_mail] if x is not None]
|
||||
return max(args)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_timeline(obj):
|
||||
timeline = []
|
||||
for comment in obj.comments.all():
|
||||
timeline.append({
|
||||
'type': 'comment',
|
||||
'id': comment.id,
|
||||
'timestamp': comment.timestamp,
|
||||
'comment': comment.comment,
|
||||
})
|
||||
for state_change in obj.state_changes.all():
|
||||
timeline.append({
|
||||
'type': 'state',
|
||||
'id': state_change.id,
|
||||
'timestamp': state_change.timestamp,
|
||||
'state': state_change.state,
|
||||
})
|
||||
for email in obj.emails.all():
|
||||
timeline.append({
|
||||
'type': 'mail',
|
||||
'id': email.id,
|
||||
'timestamp': email.timestamp,
|
||||
'sender': email.sender,
|
||||
'recipient': email.recipient,
|
||||
'subject': email.subject,
|
||||
'body': email.body,
|
||||
'attachments': AttachmentSerializer(email.attachments.all(), many=True).data,
|
||||
})
|
||||
return sorted(timeline, key=lambda x: x['timestamp'])
|
||||
|
||||
def get_queryset(self):
|
||||
return IssueThread.objects.all().order_by('-last_activity')
|
||||
from tickets.serializers import IssueSerializer, CommentSerializer
|
||||
|
||||
|
||||
class IssueViewSet(viewsets.ModelViewSet):
|
||||
|
@ -89,18 +22,6 @@ class IssueViewSet(viewsets.ModelViewSet):
|
|||
queryset = IssueThread.objects.all()
|
||||
|
||||
|
||||
class CommentSerializer(serializers.ModelSerializer):
|
||||
|
||||
def validate(self, attrs):
|
||||
if 'comment' not in attrs or attrs['comment'] == '':
|
||||
raise serializers.ValidationError('comment cannot be empty')
|
||||
return attrs
|
||||
|
||||
class Meta:
|
||||
model = Comment
|
||||
fields = ('id', 'comment', 'timestamp', 'issue_thread')
|
||||
|
||||
|
||||
class CommentViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = CommentSerializer
|
||||
queryset = Comment.objects.all()
|
||||
|
@ -116,7 +37,8 @@ def reply(request, pk):
|
|||
first_mail = Email.objects.filter(issue_thread=issue, recipient__endswith='@' + MAIL_DOMAIN).order_by(
|
||||
'timestamp').first()
|
||||
if not first_mail:
|
||||
return Response({'status': 'error', 'message': 'no previous mail found, reply would not make sense.'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response({'status': 'error', 'message': 'no previous mail found, reply would not make sense.'},
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
mail = Email.objects.create(
|
||||
issue_thread=issue,
|
||||
sender=first_mail.recipient,
|
||||
|
@ -165,17 +87,6 @@ def manual_ticket(request):
|
|||
return Response(IssueSerializer(issue).data, status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
class StateSerializer(serializers.Serializer):
|
||||
text = serializers.SerializerMethodField()
|
||||
value = serializers.SerializerMethodField()
|
||||
|
||||
def get_text(self, obj):
|
||||
return obj['text']
|
||||
|
||||
def get_value(self, obj):
|
||||
return obj['value']
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def get_available_states(request):
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
# Generated by Django 4.2.7 on 2024-01-22 16:15
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('tickets', '0007_alter_statechange_state'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='issuethread',
|
||||
options={'permissions': [('send_mail', 'Can send mail'), ('add_issuethread_manual', 'Can add issue thread manually'), ('assign_issuethread', 'Can assign issue thread')]},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='issuethread',
|
||||
name='assigned_to',
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Assignment',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('timestamp', models.DateTimeField(auto_now_add=True)),
|
||||
('assigned_to', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='assigned_tickets', to=settings.AUTH_USER_MODEL)),
|
||||
('issue_thread', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='assignments', to='tickets.issuethread')),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -1,6 +1,7 @@
|
|||
from django.db import models
|
||||
from django_softdelete.models import SoftDeleteModel
|
||||
|
||||
from authentication.models import ExtendedUser
|
||||
from inventory.models import Event
|
||||
from django.db.models.signals import post_save, pre_save
|
||||
from django.dispatch import receiver
|
||||
|
@ -32,7 +33,6 @@ 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)
|
||||
assigned_to = models.CharField(max_length=255, null=True)
|
||||
manually_created = models.BooleanField(default=False)
|
||||
|
||||
def short_uuid(self):
|
||||
|
@ -51,10 +51,24 @@ class IssueThread(SoftDeleteModel):
|
|||
return
|
||||
self.state_changes.create(state=value)
|
||||
|
||||
@property
|
||||
def assigned_to(self):
|
||||
try:
|
||||
return self.assignments.order_by('-timestamp').first().assigned_to
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
@assigned_to.setter
|
||||
def assigned_to(self, value):
|
||||
if self.assigned_to == value:
|
||||
return
|
||||
self.assignments.create(assigned_to=value)
|
||||
|
||||
class Meta:
|
||||
permissions = [
|
||||
('send_mail', 'Can send mail'),
|
||||
('add_issuethread_manual', 'Can add issue thread manually'),
|
||||
('assign_issuethread', 'Can assign issue thread'),
|
||||
]
|
||||
|
||||
|
||||
|
@ -70,12 +84,6 @@ def create_issue_thread(sender, instance, created, **kwargs):
|
|||
if created:
|
||||
StateChange.objects.create(issue_thread=instance, state='pending_new')
|
||||
|
||||
class Meta:
|
||||
permissions = [
|
||||
('send_mail', 'Can send mail'),
|
||||
('add_issuethread_manual', 'Can add issue thread manually'),
|
||||
]
|
||||
|
||||
|
||||
class Comment(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
|
@ -89,3 +97,10 @@ class StateChange(models.Model):
|
|||
issue_thread = models.ForeignKey(IssueThread, on_delete=models.CASCADE, related_name='state_changes')
|
||||
state = models.CharField(max_length=255, choices=STATE_CHOICES, default='pending_new')
|
||||
timestamp = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
|
||||
class Assignment(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
issue_thread = models.ForeignKey(IssueThread, on_delete=models.CASCADE, related_name='assignments')
|
||||
assigned_to = models.ForeignKey(ExtendedUser, on_delete=models.CASCADE, related_name='assigned_tickets')
|
||||
timestamp = models.DateTimeField(auto_now_add=True)
|
||||
|
|
106
core/tickets/serializers.py
Normal file
106
core/tickets/serializers.py
Normal file
|
@ -0,0 +1,106 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from authentication.models import ExtendedUser
|
||||
from mail.api_v2 import AttachmentSerializer
|
||||
from tickets.models import IssueThread, Comment, STATE_CHOICES
|
||||
|
||||
|
||||
class CommentSerializer(serializers.ModelSerializer):
|
||||
|
||||
def validate(self, attrs):
|
||||
if 'comment' not in attrs or attrs['comment'] == '':
|
||||
raise serializers.ValidationError('comment cannot be empty')
|
||||
return attrs
|
||||
|
||||
class Meta:
|
||||
model = Comment
|
||||
fields = ('id', 'comment', 'timestamp', 'issue_thread')
|
||||
|
||||
|
||||
class StateSerializer(serializers.Serializer):
|
||||
text = serializers.SerializerMethodField()
|
||||
value = serializers.SerializerMethodField()
|
||||
|
||||
def get_text(self, obj):
|
||||
return obj['text']
|
||||
|
||||
def get_value(self, obj):
|
||||
return obj['value']
|
||||
|
||||
|
||||
class IssueSerializer(serializers.ModelSerializer):
|
||||
timeline = serializers.SerializerMethodField()
|
||||
last_activity = serializers.SerializerMethodField()
|
||||
assigned_to = serializers.SlugRelatedField(slug_field='username', queryset=ExtendedUser.objects.all(),
|
||||
allow_null=True, required=False)
|
||||
|
||||
class Meta:
|
||||
model = IssueThread
|
||||
fields = ('id', 'timeline', 'name', 'state', 'assigned_to', 'last_activity', 'uuid')
|
||||
read_only_fields = ('id', 'timeline', 'last_activity', 'uuid')
|
||||
|
||||
def to_internal_value(self, data):
|
||||
ret = super().to_internal_value(data)
|
||||
if 'state' in data:
|
||||
ret['state'] = data['state']
|
||||
# if 'assigned_to' in data:
|
||||
# ret['assigned_to'] = data['assigned_to']
|
||||
return ret
|
||||
|
||||
def validate(self, attrs):
|
||||
if 'state' in attrs:
|
||||
if attrs['state'] not in [x[0] for x in STATE_CHOICES]:
|
||||
raise serializers.ValidationError('invalid state')
|
||||
return attrs
|
||||
|
||||
@staticmethod
|
||||
def get_last_activity(self):
|
||||
try:
|
||||
last_state_change = self.state_changes.order_by('-timestamp').first().timestamp \
|
||||
if self.state_changes.count() > 0 else None
|
||||
last_comment = self.comments.order_by('-timestamp').first().timestamp if self.comments.count() > 0 else None
|
||||
last_mail = self.emails.order_by('-timestamp').first().timestamp if self.emails.count() > 0 else None
|
||||
args = [x for x in [last_state_change, last_comment, last_mail] if x is not None]
|
||||
return max(args)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_timeline(obj):
|
||||
timeline = []
|
||||
for comment in obj.comments.all():
|
||||
timeline.append({
|
||||
'type': 'comment',
|
||||
'id': comment.id,
|
||||
'timestamp': comment.timestamp,
|
||||
'comment': comment.comment,
|
||||
})
|
||||
for state_change in obj.state_changes.all():
|
||||
timeline.append({
|
||||
'type': 'state',
|
||||
'id': state_change.id,
|
||||
'timestamp': state_change.timestamp,
|
||||
'state': state_change.state,
|
||||
})
|
||||
for email in obj.emails.all():
|
||||
timeline.append({
|
||||
'type': 'mail',
|
||||
'id': email.id,
|
||||
'timestamp': email.timestamp,
|
||||
'sender': email.sender,
|
||||
'recipient': email.recipient,
|
||||
'subject': email.subject,
|
||||
'body': email.body,
|
||||
'attachments': AttachmentSerializer(email.attachments.all(), many=True).data,
|
||||
})
|
||||
for assignment in obj.assignments.all():
|
||||
timeline.append({
|
||||
'type': 'assignment',
|
||||
'id': assignment.id,
|
||||
'timestamp': assignment.timestamp,
|
||||
'assigned_to': assignment.assigned_to.username,
|
||||
})
|
||||
return sorted(timeline, key=lambda x: x['timestamp'])
|
||||
|
||||
def get_queryset(self):
|
||||
return IssueThread.objects.all().order_by('-last_activity')
|
|
@ -297,4 +297,21 @@ class IssueApiTest(TestCase):
|
|||
)
|
||||
response = self.client.patch(f'/api/2/tickets/{issue.id}/', {'state': 'invalid'},
|
||||
content_type='application/json')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertEqual(400, response.status_code)
|
||||
|
||||
def test_assign_user(self):
|
||||
issue = IssueThread.objects.create(
|
||||
name="test issue",
|
||||
)
|
||||
response = self.client.patch(f'/api/2/tickets/{issue.id}/', {'assigned_to': self.user.username},
|
||||
content_type='application/json')
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertEqual('pending_new', response.json()['state'])
|
||||
self.assertEqual('test issue', response.json()['name'])
|
||||
self.assertEqual(self.user.username, response.json()['assigned_to'])
|
||||
timeline = response.json()['timeline']
|
||||
self.assertEqual(2, len(timeline))
|
||||
self.assertEqual('state', timeline[0]['type'])
|
||||
self.assertEqual('pending_new', timeline[0]['state'])
|
||||
self.assertEqual('assignment', timeline[1]['type'])
|
||||
self.assertEqual(self.user.username, timeline[1]['assigned_to'])
|
||||
|
|
|
@ -11,12 +11,16 @@
|
|||
<span class="timeline-item-icon faded-icon" v-else-if="item.type === 'state'" :class="'bg-' + stateInfo(item.state).color">
|
||||
<font-awesome-icon :icon="stateInfo(item.state).icon"/>
|
||||
</span>
|
||||
<span class="timeline-item-icon faded-icon" v-else-if="item.type === 'assignment'" :class="'bg-secondary'">
|
||||
<font-awesome-icon icon="user"/>
|
||||
</span>
|
||||
<span class="timeline-item-icon faded-icon" v-else>
|
||||
<font-awesome-icon icon="pen"/>
|
||||
</span>
|
||||
<TimelineMail v-if="item.type === 'mail'" :item="item"/>
|
||||
<TimelineComment v-else-if="item.type === 'comment'" :item="item"/>
|
||||
<TimelineStateChange v-else-if="item.type === 'state'" :item="item"/>
|
||||
<TimelineAssignment v-else-if="item.type === 'assignment'" :item="item"/>
|
||||
<p v-else>{{ item }}</p>
|
||||
</li>
|
||||
<li class="timeline-item">
|
||||
|
@ -61,10 +65,11 @@ import TimelineMail from "@/components/TimelineMail.vue";
|
|||
import TimelineComment from "@/components/TimelineComment.vue";
|
||||
import TimelineStateChange from "@/components/TimelineStateChange.vue";
|
||||
import {mapGetters} from "vuex";
|
||||
import TimelineAssignment from "@/components/TimelineAssignment.vue";
|
||||
|
||||
export default {
|
||||
name: 'Timeline',
|
||||
components: {TimelineStateChange, TimelineComment, TimelineMail},
|
||||
components: {TimelineAssignment, TimelineStateChange, TimelineComment, TimelineMail},
|
||||
props: {
|
||||
timeline: {
|
||||
type: Array,
|
||||
|
|
84
web/src/components/TimelineAssignment.vue
Normal file
84
web/src/components/TimelineAssignment.vue
Normal file
|
@ -0,0 +1,84 @@
|
|||
<template>
|
||||
<div class="timeline-item-description">
|
||||
<i class="avatar | small">
|
||||
<font-awesome-icon icon="user"/>
|
||||
</i>
|
||||
<span><a href="#">$USER</a> assigned ticket to <span
|
||||
class="badge badge-pill badge-secondary">{{ item.assigned_to }}</span> at <time
|
||||
:datetime="timestamp">{{ timestamp }}</time>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'TimelineAssignment',
|
||||
props: {
|
||||
'item': {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
'timestamp': function () {
|
||||
return new Date(this.item.timestamp).toLocaleString();
|
||||
},
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
|
||||
.timeline-item-description {
|
||||
display: flex;
|
||||
padding-top: 6px;
|
||||
gap: 8px;
|
||||
color: var(--gray);
|
||||
|
||||
img {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
/*color: var(--c-grey-500);*/
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
outline: 0; /* Don't actually do this */
|
||||
color: var(--info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
aspect-ratio: 1 / 1;
|
||||
flex-shrink: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
||||
&.small {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
img {
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
|
@ -41,9 +41,6 @@ export default {
|
|||
'timestamp': function () {
|
||||
return new Date(this.item.timestamp).toLocaleString();
|
||||
},
|
||||
'body': function () {
|
||||
return this.item.body.replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '<br/>');
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
|
|
@ -17,6 +17,14 @@
|
|||
<font-awesome-icon icon="clipboard"/>
|
||||
Copy DHL contact to clipboard
|
||||
</ClipboardButton>
|
||||
<div class="btn-group">
|
||||
<select class="form-control" v-model="ticket.assigned_to">
|
||||
<option v-for="user in users" :value="user.username">{{ user.username }}</option>
|
||||
</select>
|
||||
<button class="form-control btn btn-success" @click="assigTicket(ticket)">
|
||||
Assign Ticket
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<select class="form-control" v-model="ticket.state">
|
||||
<option v-for="status in state_options" :value="status.value">{{ status.text }}</option>
|
||||
|
@ -41,7 +49,7 @@ export default {
|
|||
name: 'Ticket',
|
||||
components: {ClipboardButton, Timeline},
|
||||
computed: {
|
||||
...mapState(['tickets', 'state_options']),
|
||||
...mapState(['tickets', 'state_options', 'users']),
|
||||
ticket() {
|
||||
const id = parseInt(this.$route.params.id)
|
||||
const ret = this.tickets.find(ticket => ticket.id === id);
|
||||
|
@ -53,7 +61,8 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['deleteItem', 'markItemReturned', 'loadTickets', 'sendMail', 'updateTicketPartial', 'fetchTicketStates', 'postComment']),
|
||||
...mapActions(['deleteItem', 'markItemReturned', 'sendMail', 'updateTicketPartial', 'postComment']),
|
||||
...mapActions(['loadTickets', 'loadUsers', 'fetchTicketStates']),
|
||||
handleMail(mail) {
|
||||
this.sendMail({
|
||||
id: this.ticket.id,
|
||||
|
@ -71,11 +80,18 @@ export default {
|
|||
id: ticket.id,
|
||||
state: ticket.state
|
||||
})
|
||||
},
|
||||
assigTicket(ticket) {
|
||||
this.updateTicketPartial({
|
||||
id: ticket.id,
|
||||
assigned_to: ticket.assigned_to
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchTicketStates()
|
||||
this.loadTickets()
|
||||
this.loadUsers()
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
Loading…
Reference in a new issue