mirror of
https://github.com/lemeow125/Borrowing-TrackerBackend.git
synced 2024-11-17 06:19:26 +08:00
274 lines
15 KiB
Python
274 lines
15 KiB
Python
from rest_framework import serializers, exceptions
|
|
from accounts.models import CustomUser
|
|
from equipments.models import EquipmentInstance
|
|
from .models import Transaction
|
|
from breakages.models import BreakageReport
|
|
from accounts.models import CustomUser
|
|
from config.settings import DEBUG
|
|
from django.core.cache import cache
|
|
|
|
|
|
class CustomUserSerializer(serializers.ModelSerializer):
|
|
name = serializers.CharField(source='__str__')
|
|
|
|
class Meta:
|
|
model = CustomUser
|
|
fields = ['id', 'name', 'course', 'section']
|
|
|
|
|
|
class EquipmentInstanceSerializer(serializers.ModelSerializer):
|
|
name = serializers.CharField(source='equipment.name')
|
|
|
|
class Meta:
|
|
model = EquipmentInstance
|
|
fields = ['id', 'name', 'status']
|
|
|
|
|
|
class TransactionSerializer(serializers.HyperlinkedModelSerializer):
|
|
borrower = serializers.SlugRelatedField(
|
|
many=False, slug_field='id', queryset=CustomUser.objects.all(), required=True, allow_null=False)
|
|
teacher = serializers.SlugRelatedField(
|
|
many=False, slug_field='id', queryset=CustomUser.objects.all(), required=True, allow_null=False)
|
|
equipments = serializers.SlugRelatedField(
|
|
many=True, slug_field='id', queryset=EquipmentInstance.objects.all(), required=True)
|
|
subject = serializers.CharField(required=True, allow_null=False)
|
|
additional_members = serializers.CharField(
|
|
required=False, allow_null=True, allow_blank=True)
|
|
remarks = serializers.CharField(
|
|
required=False, allow_null=True, allow_blank=True)
|
|
consumables = serializers.CharField(
|
|
required=False, allow_null=True, allow_blank=True)
|
|
timestamp = serializers.DateTimeField(
|
|
format="%m-%d-%Y %I:%M %p", read_only=True)
|
|
|
|
transaction_status = serializers.ChoiceField(
|
|
choices=Transaction.TRANSACTION_STATUS_CHOICES)
|
|
|
|
class Meta:
|
|
model = Transaction
|
|
fields = ['id', 'borrower', 'teacher', 'subject',
|
|
'equipments', 'remarks', 'transaction_status', 'additional_members', 'consumables', 'timestamp']
|
|
read_only_fields = ['id', 'timestamp']
|
|
|
|
def to_representation(self, instance):
|
|
rep = super().to_representation(instance)
|
|
rep['borrower'] = CustomUserSerializer(instance.borrower).data
|
|
rep['teacher'] = CustomUserSerializer(instance.teacher).data
|
|
rep['equipments'] = EquipmentInstanceSerializer(
|
|
instance.equipments, many=True).data
|
|
return rep
|
|
|
|
# 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):
|
|
# Any transactions created will be associated with the one sending the POST/CREATE request
|
|
user = self.context['request'].user
|
|
validated_data['borrower'] = user
|
|
|
|
# All created transactions will be labelled as Pending
|
|
validated_data['transaction_status'] = 'Pending Approval'
|
|
|
|
# If no teacher responsible for the borrow transaction is selected, raise an error
|
|
if 'teacher' not in validated_data:
|
|
raise serializers.ValidationError(
|
|
"You have not selected a teacher responsible for your borrow transaction")
|
|
|
|
# If no borrower responsible for the borrow transaction is selected, raise an error
|
|
if 'borrower' not in validated_data:
|
|
raise serializers.ValidationError(
|
|
"No borrower assigned for this transaction!")
|
|
|
|
# If the user in the teacher field is not actually a teacher, raise an error
|
|
borrower = validated_data.get('borrower')
|
|
if borrower and borrower.is_teacher or borrower.is_technician:
|
|
raise serializers.ValidationError(
|
|
"The borrower must be a student. Not a teacher or techician")
|
|
|
|
# If the user in the teacher field is not actually a teacher, raise an error
|
|
teacher = validated_data.get('teacher')
|
|
if teacher and not teacher.is_teacher:
|
|
raise serializers.ValidationError(
|
|
"The assigned teacher is not a valid teacher")
|
|
|
|
# If the user in the teacher field is not actually a teacher, raise an error
|
|
teacher = validated_data.get('teacher')
|
|
if teacher and not teacher.is_teacher:
|
|
raise serializers.ValidationError(
|
|
"The specified user is not a teacher.")
|
|
|
|
# If there are no equipments specified, raise an error
|
|
if 'equipments' in validated_data and validated_data['equipments'] == []:
|
|
raise serializers.ValidationError(
|
|
"You cannot create a transaction without any equipments selected"
|
|
)
|
|
|
|
# Check if any of the equipment instances are already in a non-finalized transaction
|
|
equipments = validated_data['equipments']
|
|
for equipment in equipments:
|
|
existing__pending_transactions = Transaction.objects.filter(
|
|
equipments=equipment, transaction_status__in=['Pending Approval', 'Approved', 'Borrowed', 'With Breakages: Pending Resolution', 'Returned: Pending Checking'])
|
|
if existing__pending_transactions.exists():
|
|
raise serializers.ValidationError(
|
|
f"Cannot add Equipment ID:{equipment.id}. It is still part of a non-finalized transaction")
|
|
|
|
# Create the transaction if there are no issues
|
|
transaction = super().create(validated_data)
|
|
|
|
# Get the equipments from the newly created transaction
|
|
equipments = transaction.equipments.all()
|
|
|
|
# Iterate through each of those equipment instances and change their status field to "Pending"
|
|
# This updates the status field of all equipment instances in a single query
|
|
EquipmentInstance.objects.filter(
|
|
id__in=[equipment.id for equipment in equipments]).update(status='Pending')
|
|
|
|
return transaction
|
|
|
|
def update(self, instance, validated_data):
|
|
cache.delete('non_finalized_transactions')
|
|
user = self.context['request'].user
|
|
|
|
# User Validation
|
|
|
|
# If user is not a teacher or a technician (ie a student), forbid them from changing the status of a transaction
|
|
if not user.is_teacher and not user.is_technician and 'transaction_status' in validated_data and validated_data.get('transaction_status') != instance.transaction_status:
|
|
raise serializers.ValidationError(
|
|
"You are not a teacher or technician. You do not have permission to change the status of transactions"
|
|
)
|
|
|
|
# If the user is a teacher but is not assigned to the current transaction, forbid them from changing anything in the transaction
|
|
if user.is_teacher and instance.teacher != user:
|
|
raise serializers.ValidationError(
|
|
"You are not the assigned teacher for this transaction"
|
|
)
|
|
|
|
# If the user is a teacher and is updating a transaction with values other than Approved or Rejected, forbid them from doing so (Only technicians can update to other statuses)
|
|
if user.is_teacher and not (validated_data.get('transaction_status') == 'Rejected' or validated_data.get('transaction_status') == 'Approved'):
|
|
raise serializers.ValidationError(
|
|
"Teachers can only mark assigned transactions as Approved or Rejected. Please consult with a Technician should you wish to process this request"
|
|
)
|
|
|
|
# Equipment Instances Validation
|
|
# Do not allow changes to equipments on created transactions
|
|
if 'equipments' in validated_data:
|
|
raise serializers.ValidationError(
|
|
"You cannot change the equipments of an already created transaction"
|
|
)
|
|
|
|
# Subject Validation
|
|
# Do not allow changes to subject on created transactions
|
|
if 'subject' in validated_data:
|
|
raise serializers.ValidationError(
|
|
"You cannot change the subject of an already created transaction"
|
|
)
|
|
|
|
# Transaction Status Validation
|
|
# Check if the update involves changing the transaction status
|
|
if 'transaction_status' in validated_data and validated_data.get('transaction_status') != instance.transaction_status:
|
|
|
|
# For already finalized/done transactions (Rejected or Finalized ones)
|
|
# Do not allow any changes to status for already finalized transactions
|
|
if instance.transaction_status in ['Rejected', 'Finalized']:
|
|
raise serializers.ValidationError(
|
|
"Unable to update rejected or finalized transaction. Please create a new one"
|
|
)
|
|
|
|
# For Pending Approval transactions
|
|
# If not changing to Approved or Rejected, throw an error
|
|
if instance.transaction_status == "Pending Approval" and not (validated_data.get('transaction_status') == "Approved" or validated_data.get('transaction_status') == "Rejected"):
|
|
raise serializers.ValidationError(
|
|
"A pending transaction can only change to Approved or Rejected"
|
|
)
|
|
|
|
# If a transaction goes from Pending Approval to Rejected, reset the status of related equipment instances so that they can be included in new transactions
|
|
if instance.transaction_status == "Pending Approval" and validated_data.get('transaction_status') == "Rejected":
|
|
equipments = instance.equipments.all()
|
|
# Iterate through each of those equipment instances and change their status field to "Available"
|
|
# This updates the status field of all equipment instances in a single query
|
|
EquipmentInstance.objects.filter(
|
|
id__in=[equipment.id for equipment in equipments]).update(status='Available')
|
|
q = super().update(instance, validated_data)
|
|
cache.delete('available_equipment_instances')
|
|
cache.delete('equipment_instances')
|
|
return q
|
|
|
|
# For Approved transactions,
|
|
# If not changing to Borrowed or Cancelled, throw an error
|
|
# Already approved transactions can only be moved to Borrowed or Cancelled
|
|
if instance.transaction_status == "Approved" and not (validated_data.get('transaction_status') == "Borrowed" or validated_data.get('transaction_status') == "Cancelled"):
|
|
raise serializers.ValidationError(
|
|
"An already approved transaction can only changed to Borrowed or Cancelled"
|
|
)
|
|
|
|
# If the transaction somehow gets Cancelled after being Approved, label the selected equipment's statuses as Available again
|
|
if instance.transaction_status == "Approved" and validated_data.get('transaction_status') == "Cancelled":
|
|
equipments = instance.equipments.all()
|
|
# Iterate through each of those equipment instances and change their status field to "Available"
|
|
# This updates the status field of all equipment instances in a single query
|
|
EquipmentInstance.objects.filter(
|
|
id__in=[equipment.id for equipment in equipments]).update(status='Available')
|
|
q = super().update(instance, validated_data)
|
|
cache.delete('available_equipment_instances')
|
|
cache.delete('equipment_instances')
|
|
return q
|
|
|
|
# If there are no issues and a transaction changes from Approved to Borrowed, label the selected equipment's statuses as Borrowed
|
|
if instance.transaction_status == "Approved" and validated_data.get('transaction_status') == "Borrowed":
|
|
equipments = instance.equipments.all()
|
|
# Iterate through each of those equipment instances and change their status field to "Borrowed"
|
|
# This updates the status field of all equipment instances in a single query
|
|
EquipmentInstance.objects.filter(
|
|
id__in=[equipment.id for equipment in equipments]).update(status='Borrowed')
|
|
return super().update(instance, validated_data)
|
|
|
|
# For Borrowed transactions,
|
|
# If not changing to Returned: Pending Checking, throw an error
|
|
# Borrowed transactions that are Returned must be checked first upon returning (thus needing Returned: Pending Checking)
|
|
if instance.transaction_status == "Borrowed" and not validated_data.get('transaction_status') == "Returned: Pending Checking":
|
|
raise serializers.ValidationError(
|
|
"A borrowed transaction can only changed to status of Finalized or With Breakages: Pending Resolution"
|
|
)
|
|
|
|
# For Return: Pending Checking transactions,
|
|
# If not changing to With Breakages: Pending Resolution or Finalized, throw an error
|
|
# Returned transactions can only be finalized without any issues or be marked as with breakages
|
|
if instance.transaction_status == "Returned: Pending Checking" and not (validated_data.get('transaction_status') == "Finalized" or validated_data.get('transaction_status') == "With Breakages: Pending Resolution"):
|
|
raise serializers.ValidationError(
|
|
"A borrowed transaction can only changed to status of Finalized or With Breakages: Pending Resolution"
|
|
)
|
|
|
|
# For transactions with pending breakage resolutions
|
|
# Do not allow updating of status. It should be updated within its respective breakage report
|
|
# If it has been resolved there, this field will automatically update to Finalized
|
|
if instance.transaction_status == "With Breakages: Pending Resolution":
|
|
raise serializers.ValidationError(
|
|
"A transaction with pending breakage resolutions must be updated or resolved in its respective breakage report"
|
|
)
|
|
|
|
# If the transaction changes from Borrowed to Finalized and there are no breakages, label the selected equipment's statuses as Available again from Borrowed
|
|
if instance.transaction_status == "Returned: Pending Checking" and validated_data.get('transaction_status') == "Finalized":
|
|
equipments = instance.equipments.all()
|
|
# Iterate through each of those equipment instances and change their status field to "Available"
|
|
# This updates the status field of all equipment instances in a single query
|
|
EquipmentInstance.objects.filter(
|
|
id__in=[equipment.id for equipment in equipments]).update(status='Available')
|
|
q = super().update(instance, validated_data)
|
|
cache.delete('available_equipment_instances')
|
|
cache.delete('equipment_instances')
|
|
return q
|
|
|
|
# If the transaction changes from Returned: Pending Checking to With Breakages, we create a Breakage Report instance
|
|
if instance.transaction_status == "Returned: Pending Checking" and validated_data.get('transaction_status') == "With Breakages: Pending Resolution":
|
|
equipments = instance.equipments.all()
|
|
report = BreakageReport.objects.create(
|
|
transaction=instance,
|
|
resolved=False
|
|
)
|
|
report.equipments.set(equipments)
|
|
return super().update(instance, validated_data)
|
|
# Changing equipment status of broken items when there are breakages is handled in breakage reports
|
|
|
|
return super().update(instance, validated_data)
|