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
2024-01-07 11:07:48 +08:00
from django . core . cache import cache
2023-12-08 23:00:15 +08:00
2023-12-14 19:27:49 +08:00
class CustomUserSerializer ( serializers . ModelSerializer ) :
name = serializers . CharField ( source = ' __str__ ' )
class Meta :
model = CustomUser
2024-01-20 19:47:15 +08:00
fields = [ ' id ' , ' name ' , ' course ' , ' section ' ]
2023-12-14 19:27:49 +08:00
class EquipmentInstanceSerializer ( serializers . ModelSerializer ) :
name = serializers . CharField ( source = ' equipment.name ' )
class Meta :
model = EquipmentInstance
2024-01-03 17:31:08 +08:00
fields = [ ' id ' , ' name ' , ' status ' ]
2023-12-14 19:27:49 +08:00
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-29 18:38:59 +08:00
subject = serializers . CharField ( required = True , allow_null = False )
2024-01-05 20:44:21 +08:00
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 )
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
2023-12-29 18:38:59 +08:00
fields = [ ' id ' , ' borrower ' , ' teacher ' , ' subject ' ,
2024-01-14 21:05:08 +08:00
' equipments ' , ' remarks ' , ' transaction_status ' , ' additional_members ' , ' consumables ' , ' 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
2023-12-27 18:36:52 +08:00
# 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
2023-12-08 23:00:15 +08:00
2023-12-09 00:38:29 +08:00
def update ( self , instance , validated_data ) :
2024-01-07 11:07:48 +08:00
cache . delete ( ' non_finalized_transactions ' )
2023-12-09 00:38:29 +08:00
user = self . context [ ' request ' ] . user
2023-12-27 18:36:52 +08:00
# 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 :
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-27 18:36:52 +08:00
# 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 :
2023-12-08 23:00:15 +08:00
raise serializers . ValidationError (
2023-12-27 18:36:52 +08:00
" You are not the assigned teacher for this transaction "
2023-12-08 23:00:15 +08:00
)
2023-12-27 18:36:52 +08:00
# 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 ' ) :
2023-12-08 23:00:15 +08:00
raise serializers . ValidationError (
2023-12-27 18:36:52 +08:00
" Teachers can only mark assigned transactions as Approved or Rejected. Please consult with a Technician should you wish to process this request "
2023-12-08 23:00:15 +08:00
)
2023-12-27 18:36:52 +08:00
# Equipment Instances Validation
# Do not allow changes to equipments on created transactions
if ' equipments ' in validated_data :
2023-12-08 23:00:15 +08:00
raise serializers . ValidationError (
2023-12-27 18:36:52 +08:00
" You cannot change the equipments of an already created transaction "
2023-12-29 18:38:59 +08:00
)
# 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 "
2023-12-08 23:00:15 +08:00
)
2023-12-27 18:36:52 +08:00
# Transaction Status Validation
2024-01-03 17:14:35 +08:00
# Check if the update involves changing the transaction status
if ' transaction_status ' in validated_data and validated_data . get ( ' transaction_status ' ) != instance . transaction_status :
2023-12-08 23:00:15 +08:00
2023-12-27 18:36:52 +08:00
# For already finalized/done transactions (Rejected or Finalized ones)
2024-01-03 17:14:35 +08:00
# Do not allow any changes to status for already finalized transactions
2023-12-27 18:36:52 +08:00
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
2023-12-08 23:00:15 +08:00
# If not changing to Approved or Rejected, throw an error
2023-12-27 18:36:52 +08:00
if instance . transaction_status == " Pending Approval " and not ( validated_data . get ( ' transaction_status ' ) == " Approved " or validated_data . get ( ' transaction_status ' ) == " Rejected " ) :
2023-12-08 23:00:15 +08:00
raise serializers . ValidationError (
" A pending transaction can only change to Approved or Rejected "
)
2023-12-27 18:36:52 +08:00
# 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 ' )
2024-01-07 12:21:14 +08:00
q = super ( ) . update ( instance , validated_data )
2024-01-07 11:11:13 +08:00
cache . delete ( ' available_equipment_instances ' )
cache . delete ( ' equipment_instances ' )
2024-01-07 12:21:14 +08:00
return q
2023-12-27 18:36:52 +08:00
2023-12-08 23:00:15 +08:00
# 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-27 18:36:52 +08:00
if instance . transaction_status == " Approved " and not ( validated_data . get ( ' transaction_status ' ) == " Borrowed " or validated_data . get ( ' transaction_status ' ) == " Cancelled " ) :
2023-12-08 23:00:15 +08:00
raise serializers . ValidationError (
2023-12-27 18:36:52 +08:00
" An already approved transaction can only changed to Borrowed or Cancelled "
2023-12-08 23:00:15 +08:00
)
2023-12-27 18:36:52 +08:00
# 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 ' )
2024-01-07 12:21:14 +08:00
q = super ( ) . update ( instance , validated_data )
2024-01-07 11:11:13 +08:00
cache . delete ( ' available_equipment_instances ' )
cache . delete ( ' equipment_instances ' )
2024-01-07 12:21:14 +08:00
return q
2023-12-27 18:36:52 +08:00
# 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 )
2023-12-08 23:00:15 +08:00
# For Borrowed transactions,
2023-12-27 18:36:52 +08:00
# 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 " :
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-27 18:36:52 +08:00
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 " ) :
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 "
)
2023-12-27 18:36:52 +08:00
# For transactions with pending breakage resolutions
2023-12-08 23:00:15 +08:00
# 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 "
)
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
2024-01-03 17:14:35 +08:00
if instance . transaction_status == " Returned: Pending Checking " and validated_data . get ( ' transaction_status ' ) == " Finalized " :
2023-12-27 18:36:52 +08:00
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 ' )
2024-01-07 12:21:14 +08:00
q = super ( ) . update ( instance , validated_data )
2024-01-07 11:07:48 +08:00
cache . delete ( ' available_equipment_instances ' )
cache . delete ( ' equipment_instances ' )
2024-01-07 12:21:14 +08:00
return q
2023-12-27 18:36:52 +08:00
# 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 (
2023-12-09 00:38:29 +08:00
transaction = instance ,
resolved = False
)
2023-12-27 18:36:52 +08:00
report . equipments . set ( equipments )
return super ( ) . update ( instance , validated_data )
2023-12-09 00:38:29 +08:00
# 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 )