mirror of
https://github.com/lemeow125/Borrowing-TrackerBackend.git
synced 2024-11-17 06:19:26 +08:00
Added breakage report model
This commit is contained in:
parent
aa078a78c5
commit
c1a7e21e95
10 changed files with 222 additions and 31 deletions
|
@ -1,3 +1,9 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from .models import BreakageReport
|
||||||
|
|
||||||
# Register your models here.
|
|
||||||
|
class BreakageReportAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('id', 'transaction', 'resolved', 'timestamp')
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(BreakageReport, BreakageReportAdmin)
|
||||||
|
|
28
equipment_tracker/breakages/migrations/0001_initial.py
Normal file
28
equipment_tracker/breakages/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# Generated by Django 4.2.7 on 2023-12-08 15:33
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('transactions', '0001_initial'),
|
||||||
|
('equipments', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BreakageReport',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('resolved', models.BooleanField(default=False)),
|
||||||
|
('timestamp', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
|
||||||
|
('equipments', models.ManyToManyField(to='equipments.equipmentinstance')),
|
||||||
|
('transaction', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='transactions.transaction')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,3 +1,46 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from accounts.models import CustomUser
|
||||||
|
from transactions.models import Transaction
|
||||||
|
from equipments.models import EquipmentInstance
|
||||||
|
from django.utils.timezone import now
|
||||||
|
|
||||||
# Create your models here.
|
|
||||||
|
class BreakageReport(models.Model):
|
||||||
|
transaction = models.ForeignKey(
|
||||||
|
Transaction, on_delete=models.CASCADE)
|
||||||
|
equipments = models.ManyToManyField(EquipmentInstance)
|
||||||
|
resolved = models.BooleanField(default=False)
|
||||||
|
timestamp = models.DateTimeField(default=now, editable=False)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Breakage report for transaction #{self.transaction.id} by {self.transaction.borrower} under {self.transaction.teacher}"
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
# Check if the instance is being updated
|
||||||
|
if not self._state.adding:
|
||||||
|
# Check if all associated equipment instances have status "Working"
|
||||||
|
all_working = all(
|
||||||
|
eq.status == 'Working' for eq in self.equipments.all())
|
||||||
|
|
||||||
|
# If all equipment instances are working
|
||||||
|
if all_working:
|
||||||
|
# set resolved field to True
|
||||||
|
self.resolved = True
|
||||||
|
# set the status of the associated transaction to "Finalized"
|
||||||
|
self.transaction.status = 'Finalized'
|
||||||
|
self.transaction.save()
|
||||||
|
|
||||||
|
# Then save the instance again to reflect the changes
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
# If not, set the resolved field to False
|
||||||
|
else:
|
||||||
|
if (self.resolved != False or self.transaction.status != 'With Breakages: Pending Resolution'):
|
||||||
|
# set resolved field to False
|
||||||
|
self.resolved = False
|
||||||
|
# set the status of the associated transaction to still be pending
|
||||||
|
self.transaction.status = 'With Breakages: Pending Resolution'
|
||||||
|
self.transaction.save()
|
||||||
|
# Then save the instance again to reflect the changes
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
67
equipment_tracker/breakages/serializers.py
Normal file
67
equipment_tracker/breakages/serializers.py
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
from accounts.models import CustomUser
|
||||||
|
from equipments.models import EquipmentInstance
|
||||||
|
from equipments.serializers import EquipmentInstanceSerializer
|
||||||
|
from .models import Transaction
|
||||||
|
from accounts.models import CustomUser
|
||||||
|
from config.settings import DEBUG
|
||||||
|
from .models import BreakageReport
|
||||||
|
|
||||||
|
|
||||||
|
class BreakageReportSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
transaction = serializers.SlugRelatedField(
|
||||||
|
many=False, slug_field='id', queryset=Transaction.objects.all(), required=True)
|
||||||
|
|
||||||
|
equipments = serializers.SlugRelatedField(
|
||||||
|
many=True, slug_field='id', queryset=EquipmentInstance.objects.all())
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = BreakageReport
|
||||||
|
fields = ['id', 'transaction', 'equipments', 'status', 'timestamp']
|
||||||
|
read_only_fields = ['id', 'timestamp']
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
transaction = validated_data.get('transaction')
|
||||||
|
equipments = validated_data.get('equipments')
|
||||||
|
user = self.context['request'].user
|
||||||
|
|
||||||
|
if 'transaction' in validated_data:
|
||||||
|
raise serializers.ValidationError({
|
||||||
|
'equipments': 'You cannot change the associated transaction for a breakage report after it has been created'
|
||||||
|
})
|
||||||
|
|
||||||
|
if 'equipments' in validated_data:
|
||||||
|
raise serializers.ValidationError({
|
||||||
|
'equipments': 'You cannot change the equipments in a breakage report after it has been created'
|
||||||
|
})
|
||||||
|
|
||||||
|
if not DEBUG:
|
||||||
|
if not user.is_teacher and 'status' in validated_data and validated_data['status'] != instance.status:
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
"You do not have permission to change the status of a breakage report"
|
||||||
|
)
|
||||||
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
def create(self, instance, validated_data):
|
||||||
|
transaction = validated_data.get('transaction')
|
||||||
|
equipments = validated_data.get('equipments')
|
||||||
|
user = self.context['request'].user
|
||||||
|
if transaction is None:
|
||||||
|
raise serializers.ValidationError({
|
||||||
|
'equipments': 'Please selected a transaction'
|
||||||
|
})
|
||||||
|
if equipments is None:
|
||||||
|
raise serializers.ValidationError({
|
||||||
|
'equipments': 'Please select equipments covered by the breakage report'
|
||||||
|
})
|
||||||
|
for equipment in equipments:
|
||||||
|
if equipment not in transaction.equipments.all():
|
||||||
|
raise serializers.ValidationError({
|
||||||
|
'equipments': 'All equipments must be associated with the specified transaction'
|
||||||
|
})
|
||||||
|
if not DEBUG:
|
||||||
|
if not user.is_teacher and 'status' in validated_data and validated_data['status'] != instance.status:
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
"You do not have permission to create a breakage report"
|
||||||
|
)
|
||||||
|
return super().create(validated_data)
|
|
@ -74,6 +74,7 @@ INSTALLED_APPS = [
|
||||||
'accounts',
|
'accounts',
|
||||||
'equipments',
|
'equipments',
|
||||||
'transactions',
|
'transactions',
|
||||||
|
'breakages',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
EQUIPMENT_CATEGORY_CHOICES = (
|
|
||||||
('Glassware', 'Glassware'),
|
|
||||||
('Miscellaneous', 'Miscellaneous')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
EQUIPMENT_INSTANCE_STATUS_CHOICES = (
|
|
||||||
('Working', 'Working'),
|
|
||||||
('Broken', 'Broken'),
|
|
||||||
('Borrowed', 'Borrowed'),
|
|
||||||
)
|
|
|
@ -3,7 +3,7 @@ from .models import Equipment, EquipmentInstance
|
||||||
from drf_spectacular.utils import extend_schema_field
|
from drf_spectacular.utils import extend_schema_field
|
||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
|
from breakages.models import BreakageReport
|
||||||
# -- Equipment Serializers
|
# -- Equipment Serializers
|
||||||
|
|
||||||
|
|
||||||
|
@ -147,7 +147,25 @@ class EquipmentInstanceSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
# Forbid user from changing equipment field once the instance is already created
|
# Forbid user from changing equipment field once the instance is already created
|
||||||
# Ignore any changes to 'equipment'
|
# Ignore any changes to 'equipment'
|
||||||
validated_data.pop('equipment', None)
|
validated_data.pop('equipment', None)
|
||||||
return super().update(instance, validated_data)
|
|
||||||
|
# This is for Breakage Report handling
|
||||||
|
# First we update the EquipmentInstance
|
||||||
|
instance = super().update(instance, validated_data)
|
||||||
|
# Then we check if the EquipmentInstance has an associated BreakageReport which is still pending
|
||||||
|
associated_breakage_report = BreakageReport.objects.filter(
|
||||||
|
equipments=instance, resolved=False).first()
|
||||||
|
# If there is one
|
||||||
|
if associated_breakage_report:
|
||||||
|
# Check if all the equipments of the currently associated BreakageReport are "Working"
|
||||||
|
all_working = all(
|
||||||
|
eq.status == 'Working' for eq in associated_breakage_report.equipments.all())
|
||||||
|
|
||||||
|
# If all the equipments are "Working", set Breakage Report to be resolved (resolved=True)
|
||||||
|
if all_working:
|
||||||
|
associated_breakage_report.resolved = True
|
||||||
|
associated_breakage_report.save()
|
||||||
|
|
||||||
|
return instance
|
||||||
|
|
||||||
# Do not allow users that are not technicians to delete equipment instances
|
# Do not allow users that are not technicians to delete equipment instances
|
||||||
def delete(self, instance):
|
def delete(self, instance):
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 4.2.7 on 2023-12-08 15:55
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('transactions', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='transaction',
|
||||||
|
name='remarks',
|
||||||
|
field=models.TextField(max_length=512, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -27,6 +27,7 @@ class Transaction(models.Model):
|
||||||
|
|
||||||
borrower = models.ForeignKey(
|
borrower = models.ForeignKey(
|
||||||
CustomUser, on_delete=models.SET_NULL, null=True, related_name='borrowed_transactions')
|
CustomUser, on_delete=models.SET_NULL, null=True, related_name='borrowed_transactions')
|
||||||
|
remarks = models.TextField(max_length=512, null=True)
|
||||||
teacher = models.ForeignKey(
|
teacher = models.ForeignKey(
|
||||||
CustomUser, on_delete=models.SET_NULL, null=True, related_name='teacher_transactions')
|
CustomUser, on_delete=models.SET_NULL, null=True, related_name='teacher_transactions')
|
||||||
equipments = models.ManyToManyField(EquipmentInstance)
|
equipments = models.ManyToManyField(EquipmentInstance)
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers, exceptions
|
||||||
from accounts.models import CustomUser
|
from accounts.models import CustomUser
|
||||||
from equipments.models import EquipmentInstance
|
from equipments.models import EquipmentInstance
|
||||||
from .models import Transaction
|
from .models import Transaction
|
||||||
|
from breakages.models import BreakageReport
|
||||||
from accounts.models import CustomUser
|
from accounts.models import CustomUser
|
||||||
from config.settings import DEBUG
|
from config.settings import DEBUG
|
||||||
|
|
||||||
|
@ -22,6 +23,11 @@ class TransactionSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
'equipments', 'transaction_status', 'timestamp']
|
'equipments', 'transaction_status', 'timestamp']
|
||||||
read_only_fields = ['id', 'timestamp']
|
read_only_fields = ['id', 'timestamp']
|
||||||
|
|
||||||
|
# Do not allow deletion of transactions
|
||||||
|
def delete(self):
|
||||||
|
raise exceptions.ValidationError(
|
||||||
|
"Deletion of transactions is not allowed. Please opt to cancel a transaction or finalize it")
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
# Any transactions created will be associated with the one sending the POST/CREATE request
|
# Any transactions created will be associated with the one sending the POST/CREATE request
|
||||||
user = self.context['request'].user
|
user = self.context['request'].user
|
||||||
|
@ -40,23 +46,23 @@ class TransactionSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
"No borrower assigned for this transaction!")
|
"No borrower assigned for this transaction!")
|
||||||
|
|
||||||
# If the user in the teacher field != actually a teacher, raise an error
|
# If the user in the teacher field is not actually a teacher, raise an error
|
||||||
borrower = validated_data.get('borrower')
|
borrower = validated_data.get('borrower')
|
||||||
if borrower and borrower.is_teacher or borrower.is_technician:
|
if borrower and borrower.is_teacher or borrower.is_technician:
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
"The borrower must be a student. Not a teacher or techician")
|
"The borrower must be a student. Not a teacher or techician")
|
||||||
|
|
||||||
# If the user in the teacher field != actually a teacher, raise an error
|
# If the user in the teacher field is not actually a teacher, raise an error
|
||||||
teacher = validated_data.get('teacher')
|
teacher = validated_data.get('teacher')
|
||||||
if teacher and not teacher.is_teacher:
|
if teacher and not teacher.is_teacher:
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
"The assigned teacher != a valid teacher")
|
"The assigned teacher is not a valid teacher")
|
||||||
|
|
||||||
# If the user in the teacher field != actually a teacher, raise an error
|
# If the user in the teacher field is not actually a teacher, raise an error
|
||||||
teacher = validated_data.get('teacher')
|
teacher = validated_data.get('teacher')
|
||||||
if teacher and not teacher.is_teacher:
|
if teacher and not teacher.is_teacher:
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
"The specified user != a teacher.")
|
"The specified user is not a teacher.")
|
||||||
|
|
||||||
# If there are no equipments specified, raise an error
|
# If there are no equipments specified, raise an error
|
||||||
if 'equipments' in validated_data and validated_data['equipments'] == []:
|
if 'equipments' in validated_data and validated_data['equipments'] == []:
|
||||||
|
@ -64,27 +70,27 @@ class TransactionSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
"You cannot create a transaction without any equipments selected"
|
"You cannot create a transaction without any equipments selected"
|
||||||
)
|
)
|
||||||
|
|
||||||
return super().create(validated_data)
|
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
|
||||||
user = self.context['request'].user
|
|
||||||
equipments = validated_data['equipments']
|
|
||||||
|
|
||||||
# Check if any of the equipment instances are already in a non-finalized transaction
|
# Check if any of the equipment instances are already in a non-finalized transaction
|
||||||
|
equipments = validated_data['equipments']
|
||||||
for equipment in equipments:
|
for equipment in equipments:
|
||||||
existing__pending_transactions = Transaction.objects.filter(
|
existing__pending_transactions = Transaction.objects.filter(
|
||||||
equipments=equipment, status__in=['Pending', 'Approved', 'Borrowed', 'With Breakages: Pending Resolution'])
|
equipments=equipment, status__in=['Pending', 'Approved', 'Borrowed', 'With Breakages: Pending Resolution'])
|
||||||
if existing__pending_transactions.exists():
|
if existing__pending_transactions.exists():
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
f"Equipment {equipment.id} is still part of a non-finalized transaction")
|
f"Cannot add Equipment #{equipment.id}. It is still part of a non-finalized transaction")
|
||||||
|
|
||||||
# If user != a teacher or a technician, forbid them from changing the status of a transaction
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
user = self.context['request'].user
|
||||||
|
|
||||||
|
# If user is not a teacher or a technician, forbid them from changing the status of a transaction
|
||||||
if not user.is_teacher and not user.technician and 'transaction_status' in validated_data and validated_data['transaction_status'] != instance.status:
|
if not user.is_teacher and not user.technician and 'transaction_status' in validated_data and validated_data['transaction_status'] != instance.status:
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
"You are not a teacher or technician. You do not have permission to change the status of transactions"
|
"You are not a teacher or technician. You do not have permission to change the status of transactions"
|
||||||
)
|
)
|
||||||
|
|
||||||
# If user != a teacher, forbid them from changing the status of a transaction
|
# If user is not a teacher, forbid them from changing the status of a transaction
|
||||||
if not user.is_teacher and 'transaction_status' in validated_data and validated_data['transaction_status'] != instance.status:
|
if not user.is_teacher and 'transaction_status' in validated_data and validated_data['transaction_status'] != instance.status:
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
"You do not have permission to change the status of a transaction"
|
"You do not have permission to change the status of a transaction"
|
||||||
|
@ -151,6 +157,20 @@ class TransactionSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
equipment.status = 'Borrowed'
|
equipment.status = 'Borrowed'
|
||||||
equipment.save()
|
equipment.save()
|
||||||
return super().update(validated_data)
|
return super().update(validated_data)
|
||||||
# Changing equipment status of broken items when returned is handled in breakage reports
|
# If the transaction changes from Borrowed to Finalized and there are no breakages, label the selected equipment's statuses as Working again from Borrowed
|
||||||
|
if instance.status == 'Borrowed' and validated_data['transaction_status'] == 'Finalized':
|
||||||
|
equipments = validated_data.get('equipments', [])
|
||||||
|
for equipment in equipments:
|
||||||
|
equipment.status = 'Working'
|
||||||
|
equipment.save()
|
||||||
|
return super().update(validated_data)
|
||||||
|
# If the transaction changes from Borrowed to With Breakages, we create a Breakage Report instance
|
||||||
|
if instance.status == 'Borrowed' and validated_data['transaction_status'] == 'Finalized':
|
||||||
|
BreakageReport.objects.create(
|
||||||
|
transaction=instance,
|
||||||
|
equipments=instance.equipments.all(),
|
||||||
|
resolved=False
|
||||||
|
)
|
||||||
|
# Changing equipment status of broken items when there are breakages is handled in breakage reports
|
||||||
|
|
||||||
return super().update(instance, validated_data)
|
return super().update(instance, validated_data)
|
||||||
|
|
Loading…
Reference in a new issue