2023-12-09 00:38:29 +08:00
from rest_framework import serializers , exceptions
2023-12-08 23:00:15 +08:00
from accounts . models import CustomUser
from equipments . models import EquipmentInstance
from . models import Transaction
2023-12-09 00:38:29 +08:00
from breakages . models import BreakageReport
2023-12-08 23:00:15 +08:00
from accounts . models import CustomUser
from config . settings import DEBUG
2023-12-14 19:27:49 +08:00
class CustomUserSerializer ( serializers . ModelSerializer ) :
name = serializers . CharField ( source = ' __str__ ' )
class Meta :
model = CustomUser
fields = [ ' id ' , ' name ' ]
class EquipmentInstanceSerializer ( serializers . ModelSerializer ) :
name = serializers . CharField ( source = ' equipment.name ' )
class Meta :
model = EquipmentInstance
fields = [ ' id ' , ' name ' ]
2023-12-08 23:00:15 +08:00
class TransactionSerializer ( serializers . HyperlinkedModelSerializer ) :
borrower = serializers . SlugRelatedField (
2023-12-14 19:27:49 +08:00
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 )
2023-12-08 23:00:15 +08:00
equipments = serializers . SlugRelatedField (
2023-12-14 19:27:49 +08:00
many = True , slug_field = ' id ' , queryset = EquipmentInstance . objects . all ( ) , required = True )
2023-12-21 15:23:28 +08:00
timestamp = serializers . DateTimeField (
format = " % m- %d - % Y % I: % M % p " , read_only = True )
2023-12-08 23:00:15 +08:00
transaction_status = serializers . ChoiceField (
choices = Transaction . TRANSACTION_STATUS_CHOICES )
class Meta :
model = Transaction
fields = [ ' id ' , ' borrower ' , ' teacher ' ,
2023-12-27 16:50:52 +08:00
' equipments ' , ' remarks ' , ' transaction_status ' , ' timestamp ' ]
2023-12-08 23:00:15 +08:00
read_only_fields = [ ' id ' , ' timestamp ' ]
2023-12-14 19:27:49 +08:00
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
2023-12-09 00:38:29 +08:00
# 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 " )
2023-12-08 23:00:15 +08:00
def create ( self , validated_data ) :
# Any transactions created will be associated with the one sending the POST/CREATE request
user = self . context [ ' request ' ] . user
2023-12-26 17:02:18 +08:00
validated_data [ ' borrower ' ] = user
2023-12-08 23:00:15 +08:00
# All created transactions will be labelled as Pending
2023-12-26 17:02:18 +08:00
validated_data [ ' transaction_status ' ] = ' Pending Approval '
2023-12-08 23:00:15 +08:00
# 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! " )
2023-12-09 00:38:29 +08:00
# If the user in the teacher field is not actually a teacher, raise an error
2023-12-08 23:00:15 +08:00
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 " )
2023-12-09 00:38:29 +08:00
# If the user in the teacher field is not actually a teacher, raise an error
2023-12-08 23:00:15 +08:00
teacher = validated_data . get ( ' teacher ' )
if teacher and not teacher . is_teacher :
raise serializers . ValidationError (
2023-12-09 00:38:29 +08:00
" The assigned teacher is not a valid teacher " )
2023-12-08 23:00:15 +08:00
2023-12-09 00:38:29 +08:00
# If the user in the teacher field is not actually a teacher, raise an error
2023-12-08 23:00:15 +08:00
teacher = validated_data . get ( ' teacher ' )
if teacher and not teacher . is_teacher :
raise serializers . ValidationError (
2023-12-09 00:38:29 +08:00
" The specified user is not a teacher. " )
2023-12-08 23:00:15 +08:00
# 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
2023-12-09 00:38:29 +08:00
equipments = validated_data [ ' equipments ' ]
2023-12-08 23:00:15 +08:00
for equipment in equipments :
existing__pending_transactions = Transaction . objects . filter (
2023-12-26 17:02:18 +08:00
equipments = equipment , transaction_status__in = [ ' Pending Approval ' , ' Approved ' , ' Borrowed ' , ' With Breakages: Pending Resolution ' , ' Returned: Pending Checking ' ] )
2023-12-08 23:00:15 +08:00
if existing__pending_transactions . exists ( ) :
raise serializers . ValidationError (
2023-12-26 17:02:18 +08:00
f " Cannot add Equipment ID: { equipment . id } . It is still part of a non-finalized transaction " )
2023-12-09 00:38:29 +08:00
return super ( ) . create ( validated_data )
2023-12-08 23:00:15 +08:00
2023-12-09 00:38:29 +08:00
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
2023-12-16 15:00:13 +08:00
if not user . is_teacher and not user . is_technician and ' transaction_status ' in validated_data and validated_data [ ' transaction_status ' ] != instance . transaction_status :
2023-12-08 23:00:15 +08:00
raise serializers . ValidationError (
" You are not a teacher or technician. You do not have permission to change the status of transactions "
)
2023-12-09 00:38:29 +08:00
# If user is not a teacher, forbid them from changing the status of a transaction
2023-12-16 15:00:13 +08:00
if not user . is_teacher and ' transaction_status ' in validated_data and validated_data [ ' transaction_status ' ] != instance . transaction_status :
2023-12-08 23:00:15 +08:00
raise serializers . ValidationError (
" You do not have permission to change the status of a transaction "
)
# Do not allow changes to equipments on created transactions
if ' equipments ' in validated_data and instance . equipments != validated_data [ ' equipments ' ] :
raise serializers . ValidationError (
" You cannot change the equipments of an already created transaction "
)
# For already finalized/done transactions (Rejected or Finalized ones)
# Do not allow any changes to already finalized transactions
2023-12-16 15:00:13 +08:00
if instance . transaction_status in [ ' Rejected ' , ' Finalized ' ] :
2023-12-08 23:00:15 +08:00
raise serializers . ValidationError (
" Unable to update rejected or finalized transaction. Please create a new one "
)
# Check if the update involves the transaction status
if ' transaction_status ' in validated_data :
# For Pending transactions
# If not changing to Approved or Rejected, throw an error
2023-12-16 15:00:13 +08:00
if instance . transaction_status == " Pending " and ( validated_data [ ' transaction_status ' ] != " Approved " or validated_data [ ' transaction_status ' ] != " Rejected " ) :
2023-12-08 23:00:15 +08:00
raise serializers . ValidationError (
" A pending transaction can only change to Approved or Rejected "
)
# For Approved transactions,
# If not changing to Borrowed or Cancelled, throw an error
# Already approved transactions can only be moved to Borrowed or Cancelled
2023-12-16 15:00:13 +08:00
if instance . transaction_status == " Approved " and ( validated_data [ ' transaction_status ' ] != " Borrowed " or validated_data != " Cancelled " ) :
2023-12-08 23:00:15 +08:00
raise serializers . ValidationError (
" An already approved transaction can only changed to Borrowed (On borrow) or Cancelled "
)
# For Borrowed transactions,
# If not changing to returned, throw an error
# Borrowed transactions that can only be changed to returned, pending checking for broken items
2023-12-16 15:00:13 +08:00
if instance . transaction_status == " Borrowed " and ( validated_data [ ' transaction_status ' ] != " Finalized " or validated_data != " With Breakages: Pending Resolution " ) :
2023-12-08 23:00:15 +08:00
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
2023-12-16 15:00:13 +08:00
if instance . transaction_status == " Returned: Pending Checking " and ( validated_data [ ' transaction_status ' ] != " Finalized " or validated_data != " With Breakages: Pending Resolution " ) :
2023-12-08 23:00:15 +08:00
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
2023-12-16 15:00:13 +08:00
if instance . transaction_status == " With Breakages: Pending Resolution " :
2023-12-08 23:00:15 +08:00
raise serializers . ValidationError (
" A transaction with pending breakage resolutions must be updated or resolved in its respective breakage report "
)
# If there are no issues and a transaction changes from Approved to Borrowed, label the selected equipment's statuses as Borrowed
2023-12-16 15:00:13 +08:00
if instance . transaction_status == " Approved " and validated_data [ ' transaction_status ' ] == " Borrowed " :
2023-12-08 23:00:15 +08:00
equipments = validated_data . get ( ' equipments ' , [ ] )
for equipment in equipments :
2023-12-16 15:00:13 +08:00
equipment . transaction_status = ' Borrowed '
2023-12-08 23:00:15 +08:00
equipment . save ( )
return super ( ) . update ( validated_data )
2023-12-16 15:00:13 +08:00
# 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 == " Borrowed " and validated_data [ ' transaction_status ' ] == " Finalized " :
2023-12-09 00:38:29 +08:00
equipments = validated_data . get ( ' equipments ' , [ ] )
for equipment in equipments :
2023-12-16 15:00:13 +08:00
equipment . transaction_status = ' Available '
2023-12-09 00:38:29 +08:00
equipment . save ( )
return super ( ) . update ( validated_data )
# If the transaction changes from Borrowed to With Breakages, we create a Breakage Report instance
2023-12-16 15:00:13 +08:00
if instance . transaction_status == " Borrowed " and validated_data [ ' transaction_status ' ] == " Finalized " :
2023-12-09 00:38:29 +08:00
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
2023-12-08 23:00:15 +08:00
return super ( ) . update ( instance , validated_data )