mirror of
https://github.com/lemeow125/Borrowing-TrackerBackend.git
synced 2025-04-27 10:11:24 +08:00
Added transactions and updated models to reflect transactions
This commit is contained in:
parent
d915852632
commit
2c8cc87cbe
39 changed files with 635 additions and 291 deletions
0
equipment_tracker/transactions/__init__.py
Normal file
0
equipment_tracker/transactions/__init__.py
Normal file
16
equipment_tracker/transactions/admin.py
Normal file
16
equipment_tracker/transactions/admin.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
from django.contrib import admin
|
||||
from .models import Transaction
|
||||
from accounts.models import CustomUser
|
||||
|
||||
|
||||
class TransactionAdmin(admin.ModelAdmin):
|
||||
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
||||
if db_field.name == "borrower":
|
||||
kwargs["queryset"] = CustomUser.objects.exclude(
|
||||
is_technician=True).exclude(is_teacher=True)
|
||||
elif db_field.name == "teacher":
|
||||
kwargs["queryset"] = CustomUser.objects.filter(is_teacher=True)
|
||||
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
||||
|
||||
|
||||
admin.site.register(Transaction, TransactionAdmin)
|
6
equipment_tracker/transactions/apps.py
Normal file
6
equipment_tracker/transactions/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class TransactionsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'transactions'
|
30
equipment_tracker/transactions/migrations/0001_initial.py
Normal file
30
equipment_tracker/transactions/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Generated by Django 4.2.7 on 2023-12-08 14:41
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('equipments', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Transaction',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('transaction_status', models.CharField(choices=[('Pending Approval', 'Pending Approval'), ('Approved', 'Approved'), ('Rejected', 'Rejected'), ('Cancelled', 'Cancelled'), ('Borrowed', 'Borrowed'), ('Returned: Pending Checking', 'Returned: Pending Checking'), ('With Breakages: Pending Resolution', 'With Breakages: Pending Resolution'), ('Finalized', 'Finalized')], default='Pending', max_length=40)),
|
||||
('timestamp', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
|
||||
('borrower', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='borrowed_transactions', to=settings.AUTH_USER_MODEL)),
|
||||
('equipments', models.ManyToManyField(to='equipments.equipmentinstance')),
|
||||
('teacher', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='teacher_transactions', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
0
equipment_tracker/transactions/migrations/__init__.py
Normal file
0
equipment_tracker/transactions/migrations/__init__.py
Normal file
38
equipment_tracker/transactions/models.py
Normal file
38
equipment_tracker/transactions/models.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
from django.db import models
|
||||
from accounts.models import CustomUser
|
||||
from equipments.models import EquipmentInstance
|
||||
from django.utils.timezone import now
|
||||
|
||||
|
||||
class Transaction(models.Model):
|
||||
TRANSACTION_STATUS_CHOICES = (
|
||||
# Transaction is pending approval
|
||||
('Pending Approval', 'Pending Approval'),
|
||||
# Transaction has been approved, pending delivery by labtech
|
||||
('Approved', 'Approved'),
|
||||
# Tranasction has been rejected
|
||||
('Rejected', 'Rejected'),
|
||||
# Transaction has been approved but has been cancelled due to rare circumstances
|
||||
('Cancelled', 'Cancelled'),
|
||||
# Transaction has been delivered and is on borrow
|
||||
('Borrowed', 'Borrowed'),
|
||||
# Transaction has been returned, pending checking
|
||||
('Returned: Pending Checking', 'Returned: Pending Checking'),
|
||||
# Transaction has been breakages after being returned, pending resolution
|
||||
('With Breakages: Pending Resolution',
|
||||
'With Breakages: Pending Resolution'),
|
||||
# Transaction has been finalized
|
||||
('Finalized', 'Finalized'),
|
||||
)
|
||||
|
||||
borrower = models.ForeignKey(
|
||||
CustomUser, on_delete=models.SET_NULL, null=True, related_name='borrowed_transactions')
|
||||
teacher = models.ForeignKey(
|
||||
CustomUser, on_delete=models.SET_NULL, null=True, related_name='teacher_transactions')
|
||||
equipments = models.ManyToManyField(EquipmentInstance)
|
||||
transaction_status = models.CharField(
|
||||
max_length=40, choices=TRANSACTION_STATUS_CHOICES, default='Pending')
|
||||
timestamp = models.DateTimeField(default=now, editable=False)
|
||||
|
||||
def __str__(self):
|
||||
return f"Transaction #{self.id} under {self.teacher} by {self.borrower}"
|
156
equipment_tracker/transactions/serializers.py
Normal file
156
equipment_tracker/transactions/serializers.py
Normal file
|
@ -0,0 +1,156 @@
|
|||
from rest_framework import serializers
|
||||
from accounts.models import CustomUser
|
||||
from equipments.models import EquipmentInstance
|
||||
from .models import Transaction
|
||||
from accounts.models import CustomUser
|
||||
from config.settings import DEBUG
|
||||
|
||||
|
||||
class TransactionSerializer(serializers.HyperlinkedModelSerializer):
|
||||
borrower = serializers.SlugRelatedField(
|
||||
many=False, slug_field='id', queryset=CustomUser.objects.all(), required=False, allow_null=True)
|
||||
|
||||
equipments = serializers.SlugRelatedField(
|
||||
many=True, slug_field='id', queryset=EquipmentInstance.objects.filter(status="Working"), required=False)
|
||||
|
||||
transaction_status = serializers.ChoiceField(
|
||||
choices=Transaction.TRANSACTION_STATUS_CHOICES)
|
||||
|
||||
class Meta:
|
||||
model = Transaction
|
||||
fields = ['id', 'borrower', 'teacher',
|
||||
'equipments', 'transaction_status', 'timestamp']
|
||||
read_only_fields = ['id', 'timestamp']
|
||||
|
||||
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.data['borrower'] = user
|
||||
|
||||
# All created transactions will be labelled as Pending
|
||||
validated_data['transaction_status'] = 'Pending'
|
||||
|
||||
# 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 != 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 != actually a teacher, raise an error
|
||||
teacher = validated_data.get('teacher')
|
||||
if teacher and not teacher.is_teacher:
|
||||
raise serializers.ValidationError(
|
||||
"The assigned teacher != a valid teacher")
|
||||
|
||||
# If the user in the teacher field != actually a teacher, raise an error
|
||||
teacher = validated_data.get('teacher')
|
||||
if teacher and not teacher.is_teacher:
|
||||
raise serializers.ValidationError(
|
||||
"The specified user != 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"
|
||||
)
|
||||
|
||||
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
|
||||
for equipment in equipments:
|
||||
existing__pending_transactions = Transaction.objects.filter(
|
||||
equipments=equipment, status__in=['Pending', 'Approved', 'Borrowed', 'With Breakages: Pending Resolution'])
|
||||
if existing__pending_transactions.exists():
|
||||
raise serializers.ValidationError(
|
||||
f"Equipment {equipment.id} is still part of a non-finalized transaction")
|
||||
|
||||
# If user != 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:
|
||||
raise serializers.ValidationError(
|
||||
"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 not user.is_teacher and 'transaction_status' in validated_data and validated_data['transaction_status'] != instance.status:
|
||||
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
|
||||
if instance.status in ['Rejected', 'Finalized']:
|
||||
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
|
||||
if instance.status == 'Pending' and validated_data['transaction_status'] != 'Approved' or validated_data['transaction_status'] != 'Rejected':
|
||||
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
|
||||
if instance.status == 'Approved' and validated_data['transaction_status'] != 'Borrowed' or validated_data != 'Cancelled':
|
||||
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
|
||||
if instance.status == 'Borrowed' and validated_data['transaction_status'] != 'Finalized' or validated_data != 'With Breakages: Pending Resolution':
|
||||
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.status == 'Returned: Pending Checking' and validated_data['transaction_status'] != 'Finalized' or validated_data != '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.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 there are no issues and a transaction changes from Approved to Borrowed, label the selected equipment's statuses as Borrowed
|
||||
if instance.status == 'Approved' and validated_data['transaction_status'] == 'Borrowed':
|
||||
equipments = validated_data.get('equipments', [])
|
||||
for equipment in equipments:
|
||||
equipment.status = 'Borrowed'
|
||||
equipment.save()
|
||||
return super().update(validated_data)
|
||||
# Changing equipment status of broken items when returned is handled in breakage reports
|
||||
|
||||
return super().update(instance, validated_data)
|
3
equipment_tracker/transactions/tests.py
Normal file
3
equipment_tracker/transactions/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
10
equipment_tracker/transactions/urls.py
Normal file
10
equipment_tracker/transactions/urls.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from django.urls import include, path
|
||||
from rest_framework import routers
|
||||
from . import views
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router.register(r'', views.TransactionViewSet)
|
||||
|
||||
urlpatterns = [
|
||||
path('', include(router.urls)),
|
||||
]
|
13
equipment_tracker/transactions/views.py
Normal file
13
equipment_tracker/transactions/views.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework import viewsets, generics
|
||||
from .serializers import TransactionSerializer
|
||||
from .models import Transaction
|
||||
|
||||
|
||||
class TransactionViewSet(viewsets.ModelViewSet):
|
||||
# Only allow GET, POST/CREATE
|
||||
# Transactions cannot be deleted
|
||||
http_method_names = ['get', 'post']
|
||||
permission_classes = [IsAuthenticated]
|
||||
serializer_class = TransactionSerializer
|
||||
queryset = Transaction.objects.all()
|
Loading…
Add table
Add a link
Reference in a new issue