Added breakage report model

This commit is contained in:
Keannu Bernasol 2023-12-09 00:38:29 +08:00
parent aa078a78c5
commit c1a7e21e95
10 changed files with 222 additions and 31 deletions

View file

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

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

View file

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

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

View file

@ -74,6 +74,7 @@ INSTALLED_APPS = [
'accounts', 'accounts',
'equipments', 'equipments',
'transactions', 'transactions',
'breakages',
] ]
MIDDLEWARE = [ MIDDLEWARE = [

View file

@ -1,11 +0,0 @@
EQUIPMENT_CATEGORY_CHOICES = (
('Glassware', 'Glassware'),
('Miscellaneous', 'Miscellaneous')
)
EQUIPMENT_INSTANCE_STATUS_CHOICES = (
('Working', 'Working'),
('Broken', 'Broken'),
('Borrowed', 'Borrowed'),
)

View file

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

View file

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

View file

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

View file

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