diff --git a/FusionIIIT/applications/iwdModuleV2/api/serializers.py b/FusionIIIT/applications/iwdModuleV2/api/serializers.py new file mode 100644 index 000000000..14cb9c975 --- /dev/null +++ b/FusionIIIT/applications/iwdModuleV2/api/serializers.py @@ -0,0 +1,96 @@ +from rest_framework import serializers +from applications.globals.models import * +from applications.iwdModuleV2.models import * +from applications.ps1.models import * +from decimal import Decimal +import json +class WorkOrderFormSerializer(serializers.ModelSerializer): + class Meta: + model = WorkOrder + fields = '__all__' + +class DesignationSerializer(serializers.ModelSerializer): + class Meta: + model = Designation + fields = ['id', 'name'] + +class HoldsDesignationSerializer(serializers.ModelSerializer): + designation = DesignationSerializer() + username = serializers.CharField(source='user.username') + + class Meta: + model = HoldsDesignation + fields = ['id', 'designation', 'username'] + +class CreateRequestsSerializer(serializers.ModelSerializer): + class Meta: + model = Requests + fields = ['id', 'name', 'area', 'description', 'requestCreatedBy'] + + def create(self, validated_data): + validated_data['engineerProcessed'] = 0 + validated_data['iwdAdminApproval'] = 0 + validated_data['directorApproval'] = 0 + validated_data['deanProcessed'] = 0 + validated_data['status'] = "Pending" + validated_data['issuedWorkOrder'] = 0 + validated_data['workCompleted'] = 0 + validated_data['billGenerated'] = 0 + validated_data['billProcessed'] = 0 + validated_data['billSettled'] = 0 + return super().create(validated_data) + +class IWDAdminApprovedRequestsSerializer(serializers.ModelSerializer): + class Meta: + model = Requests + fields = ['id', 'name', 'area', 'description', 'requestCreatedBy'] + +class DirectorApprovedRequestsSerializer(serializers.ModelSerializer): + class Meta: + model = Requests + fields = ['id', 'name', 'area', 'description', 'requestCreatedBy'] + +class WorkUnderProgressSerializer(serializers.ModelSerializer): + class Meta: + model = Requests + fields = ['id', 'name', 'area', 'description', 'requestCreatedBy', 'issuedWorkOrder', 'workCompleted'] + + +class RequestsInProgressSerializer(serializers.ModelSerializer): + class Meta: + model = Requests + fields = ['id', 'name', 'area', 'description', 'requestCreatedBy', 'issuedWorkOrder', 'workCompleted'] + +class ItemsSerializer(serializers.ModelSerializer): + class Meta: + model = Item + fields = ['name', 'description', 'unit', 'price_per_unit', 'quantity', 'docs', 'total_price', 'id'] + + +class CreateProposalSerializer(serializers.ModelSerializer): + items = ItemsSerializer(many=True, write_only=True) # Keep the many=True option + + class Meta: + model = Proposal + fields = '__all__' + + def create(self, validated_data): + items_data = validated_data.pop('items', []) + proposal = Proposal.objects.create(**validated_data) + proposal.save() + return proposal + +class ProposalSerializer(serializers.ModelSerializer): + class Meta: + model = Proposal + fields = '__all__' + + +class VendorSerializer(serializers.ModelSerializer): + class Meta: + model = Vendor + fields = '__all__' + def create(self, validated_data): + vendor = Vendor.objects.create(**validated_data) + vendor.save() + return vendor \ No newline at end of file diff --git a/FusionIIIT/applications/iwdModuleV2/api/urls.py b/FusionIIIT/applications/iwdModuleV2/api/urls.py new file mode 100644 index 000000000..b873b2c41 --- /dev/null +++ b/FusionIIIT/applications/iwdModuleV2/api/urls.py @@ -0,0 +1,49 @@ +from django.urls import path +from rest_framework.routers import DefaultRouter +from . import views + +router = DefaultRouter() + +# Main resources +router.register("requests", views.RequestViewSet, basename="requests") +router.register("budgets", views.BudgetViewSet, basename="budgets") +router.register("vendors", views.VendorViewSet, basename="vendors") +router.register("work", views.WorkViewSet, basename="work") + +urlpatterns = router.urls + [ + + # Request workflows + path("requests//forward/", views.forward_request, name="forward-request"), + path("requests//director-approval/", views.handle_director_approval, name="director-approval"), + path("requests//admin-approval/", views.handle_admin_approval, name="admin-approval"), + path("requests//dean-process/", views.handle_dean_process_request, name="dean-process"), + + # Status endpoints + path("requests-status/", views.requests_status, name="requests-status"), + path("rejected-requests/", views.rejected_requests, name="rejected-requests"), + + # Work progress + path("work/issued/", views.get_issued_work, name="issued-work"), + path("work/progress/", views.work_under_progress, name="work-under-progress"), + path("work/completed/", views.work_completed, name="work-completed"), + + # Vendor & proposal endpoints + path("proposals/", views.get_proposals, name="proposals"), + path("items/", views.get_items, name="items"), + + # Budget APIs + path("budget/add/", views.add_budget, name="add-budget"), + path("budget/edit/", views.edit_budget, name="edit-budget"), + path("budget/view/", views.view_budget, name="view-budget"), + + # Audit APIs + path("audit/", views.handle_audit_document, name="audit-document"), + path("audit/view/", views.audit_document_view, name="audit-document-view"), + + # Billing APIs + path("bills/process/", views.handle_process_bills, name="process-bills"), + path("bills/generated/", views.generatedBillsView, name="generated-bills"), + path("bills/settle/", views.settle_bills_view, name="settle-bills"), + path("bills/settle-request/", views.handle_settle_bill_requests, name="settle-bills-request"), + +] \ No newline at end of file diff --git a/FusionIIIT/applications/iwdModuleV2/api/views.py b/FusionIIIT/applications/iwdModuleV2/api/views.py new file mode 100644 index 000000000..640a74d1f --- /dev/null +++ b/FusionIIIT/applications/iwdModuleV2/api/views.py @@ -0,0 +1,1176 @@ +from rest_framework.decorators import api_view, permission_classes +from rest_framework.response import Response +from rest_framework import status +from rest_framework.permissions import IsAuthenticated +from applications.globals.models import * +from applications.iwdModuleV2.models import * +from applications.ps1.models import * +from applications.filetracking.sdk.methods import * +from notification.views import iwd_notif +from .serializers import * +from django.shortcuts import get_object_or_404 +from django.contrib import messages +from reportlab.lib.pagesizes import letter +from reportlab.pdfgen import canvas +from reportlab.platypus import Table, TableStyle +from reportlab.lib import colors +from io import BytesIO +from django.http import HttpResponse +from django.core.exceptions import ObjectDoesNotExist +from collections import defaultdict +# @api_view(['GET']) +# def dashboard(request): +# userObj = request.user +# userDesignationObjects = HoldsDesignation.objects.filter(user=userObj) +# eligible = any(p.designation.name == 'Admin IWD' for p in userDesignationObjects) +# return Response({'eligible': eligible}) + +''' + Fully Implemented +''' + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def fetch_designations(request): + ''' + to return a list of cincerned designations in the module's scope + ''' + holdsDesignations = [] + + designations = Designation.objects.filter(name__in=designations_list) + + for designation in designations: + holds = HoldsDesignation.objects.filter(designation=designation) + serializer = HoldsDesignationSerializer(holds, many=True) + holdsDesignations.extend(serializer.data) + + return Response({'holdsDesignations': holdsDesignations}, status=status.HTTP_200_OK) + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def create_request(request): + + ''' + to create a new request + ''' + data = request.data.copy() + data['requestCreatedBy'] = request.user.username + attachment = request.FILES.get('file') + serializer = CreateRequestsSerializer(data=data, context={'request': request}) + if serializer.is_valid(): + formObject = serializer.save() + receiver_desg = "Admin IWD" + receiver_user = "kunal" + # receiver_desg, receiver_user = data.get('designation').split('|') + try: + receiver_user_obj = User.objects.get(username=receiver_user) + request_object = Requests.objects.get(pk=formObject.pk) + except User.DoesNotExist: + return Response({'error': 'Receiver user does not exist'}, status=status.HTTP_400_BAD_REQUEST) + create_file( + uploader=request.user.username, + uploader_designation=data.get('role'), + receiver=receiver_user, + receiver_designation=receiver_desg, + src_module="IWD", + src_object_id=str(request_object.id), + file_extra_JSON={"value": 2}, + attached_file=attachment + ) + + + iwd_notif(request.user, receiver_user_obj, "Request_added") + + return Response({'message': "Request Successfully Created"}, status=status.HTTP_201_CREATED) + + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def created_requests(request): + + ''' + to get a list of requests in current user's inbox + ''' + + params = request.query_params + obj = [] + inbox_files = view_inbox( + username=request.user, + designation=params.get('role'), + src_module="IWD" + ) + for result in inbox_files: + src_object_id = result['src_object_id'] + request_object = Requests.objects.filter(id=src_object_id).first() + if request_object: + file_obj = get_object_or_404(File, src_object_id=request_object.id, src_module="IWD") + element = { + 'request_id': request_object.id, + 'name': request_object.name, + 'area': request_object.area, + 'description': request_object.description, + 'requestCreatedBy': request_object.requestCreatedBy, + 'file_id': file_obj.id, + 'directorApproval': request_object.directorApproval, + 'processed_by_dean': request_object.deanProcessed, + } + obj.append(element) + + return Response(obj, status=200) + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def view_file(request): + + ''' + get complete file data and track records + ''' + + params = request.query_params + id = params.get('file_id') + file1 = get_object_or_404(File, id=id) + + tracks = Tracking.objects.filter(file_id=file1) + file_serializer = FileSerializer(file1) + tracks_serializer = TrackingSerializer(tracks, many=True) + return Response({ + "file": file_serializer.data, + "tracks": tracks_serializer.data, + "url": "url", + }, status=200) + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def dean_processed_requests(request): + + ''' + to get requests that have been processed through the dean and are ready for director's approval + ''' + + obj = [] + params = request.query_params + desg = params.get('role') + + inbox_files = view_inbox( + username=request.user.username, + designation=desg, + src_module="IWD" + ) + + for result in inbox_files: + src_object_id = result['src_object_id'] + request_object = Requests.objects.filter(id=src_object_id, directorApproval=0).first() + file_obj = File.objects.get(src_object_id=src_object_id, src_module="IWD") + if request_object: + element = { + 'request_id': request_object.id, + 'name': request_object.name, + 'area': request_object.area, + 'description': request_object.description, + 'requestCreatedBy': request_object.requestCreatedBy, + 'file_id': file_obj.id, + 'directorApproval': request_object.directorApproval, + } + obj.append(element) + + return Response(obj) + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def handle_dean_process_request(request): + + ''' + This api is made for the dean to process and forward the request + ''' + + data = request.data + fileid = data.get('fileid') + request_id = File.objects.get(id=fileid).src_object_id + + remarks = data.get('remarks') + attachment = request.FILES.get('file') + receiver_desg, receiver_user = data.get('designation').split('|') + forward_file( + file_id=fileid, + receiver=receiver_user, + receiver_designation=receiver_desg, + file_extra_JSON={"message": "Request forwarded."}, + remarks=remarks, + file_attachment=attachment, + ) + + Requests.objects.filter(id=request_id).update(deanProcessed=1, status="Approved by the dean", directorApproval=0) + receiver_user_obj = get_object_or_404(User, username=receiver_user) + iwd_notif(request.user, receiver_user_obj, "file_forward") + return Response({'message': 'File Forwarded'}, status=200) + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def forward_request(request): + data = request.data + fileid = data.get('fileid') + request_id = File.objects.get(id=fileid).src_object_id + + remarks = data.get('remarks') + attachment = request.FILES.get('file') + receiver_desg, receiver_user = data.get('designation').split('|') + forward_file( + file_id=fileid, + receiver=receiver_user, + receiver_designation=receiver_desg, + file_extra_JSON={"message": "Request forwarded."}, + remarks=remarks, + file_attachment=attachment, + ) + + receiver_user_obj = get_object_or_404(User, username=receiver_user) + iwd_notif(request.user, receiver_user_obj, "file_forward") + + return Response({ + "message": "File forwarded successfully", + }, status=status.HTTP_200_OK) + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def handle_director_approval(request): + """ + Approve or reject a request by the director. + """ + data = request.data + fileid = data.get('fileid') + action = data.get('action') + + if not fileid or not action: + return Response({'error': 'File ID and action are required'}, status=status.HTTP_400_BAD_REQUEST) + + try: + request_id = File.objects.get(id=fileid).src_object_id + except File.DoesNotExist: + return Response({'error': 'File not found'}, status=status.HTTP_404_NOT_FOUND) + + request_instance = Requests.objects.filter(id=request_id, iwdAdminApproval=True).first() + if not request_instance: + return Response({'error': 'Request not approved by IWD Admin'}, status=status.HTTP_400_BAD_REQUEST) + + if not request_instance.activeProposal: + return Response({'error': 'No active proposal exists for this request'}, status=status.HTTP_400_BAD_REQUEST) + + remarks = data.get('remarks') + attachment = request.FILES.get('file') + receiver_desg, receiver_user = data.get('designation').split('|') + + forward_file( + file_id=fileid, + receiver=receiver_user, + receiver_designation=receiver_desg, + file_extra_JSON={"message": "Request forwarded."}, + remarks=remarks, + file_attachment=attachment, + ) + receiver_user_obj = get_object_or_404(User, username=receiver_user) + iwd_notif(request.user, receiver_user_obj, "file_forward") + + if action == "approve": + Requests.objects.filter(id=request_id).update(directorApproval=1, status="Approved by the director") + return Response({'message': 'Request approved by Director'}, status=status.HTTP_200_OK) + elif action == "reject": + Requests.objects.filter(id=request_id).update(directorApproval=-1, status="Rejected by the director", iwdAdminApproval=0, activeProposal=None) + return Response({'message': 'Request rejected by Director'}, status=status.HTTP_200_OK) + else: + return Response({'error': 'Invalid action'}, status=status.HTTP_400_BAD_REQUEST) + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def handle_audit_document(request): + + ''' + This api is used to audit bill documents (with provided fileid) + ''' + + fileid = request.data.get('fileid') + remarks = request.data.get('remarks') + attachment = request.FILES.get('attachment') + receiver_desg, receiver_user = request.data['designation'].split('|') + + if fileid: + request_id = File.objects.get(id=fileid).src_object_id + + forward_file( + file_id=fileid, + receiver=receiver_user, + receiver_designation=receiver_desg, + file_extra_JSON={"message": "Request forwarded."}, + remarks=remarks, + file_attachment=attachment, + ) + + Requests.objects.filter(id=request_id).update(status="Bill Audited") + + return Response("Bill Audited", status=status.HTTP_200_OK) + + return Response({'error': 'File ID not provided'}, status=status.HTTP_400_BAD_REQUEST) + + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def rejected_requests(request): + + ''' + get requests rejected by director (-1) + ''' + + obj = [] + desg = request.query_params.get('role') + + inbox_files = view_inbox( + username=request.user, + designation=desg, + src_module="IWD" + ) + + for result in inbox_files: + src_object_id = result['src_object_id'] + if src_object_id==None: + continue + request_object = Requests.objects.filter(id=src_object_id, directorApproval=-1).first() + if request_object: + element = { + 'id': request_object.id, + 'name': request_object.name, + 'area': request_object.area, + 'description': request_object.description, + 'requestCreatedBy': request_object.requestCreatedBy + } + obj.append(element) + + return Response(obj, status=status.HTTP_200_OK) + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def handle_update_requests(request): + + ''' + to update an old request(delete and make a new one) + ''' + + data = request.data.copy() + request_id = data.get("id") + request_instance = Requests.objects.filter(id=request_id).first() + if not request_instance: + return Response({'error': 'Request not found'}, status=status.HTTP_404_NOT_FOUND) + + if request_instance.iwdAdminApproval == -1: + return Response({'error': 'This request has been rejected by IWD Admin and cannot be updated.'}, status=status.HTTP_403_FORBIDDEN) + + receiver_desg, receiver_user = data.get("designation").split('|') + data["created_by"] = str(request.user) + data["request"] = request_id + if request.FILES.get("supporting_documents"): + data["supporting_documents"] = request.FILES["supporting_documents"] + items = defaultdict(dict) + for key in request.data: + if key.startswith("items["): + import re + match = re.match(r"items\[(\d+)\]\[(\w+)\]", key) + if match: + index, field = match.groups() + value = request.data[key] + if field in ['quantity', 'price_per_unit']: # Cast numbers + try: + value = Decimal(value) + except: + pass + items[int(index)][field] = value + + for key in request.FILES: + if key.startswith("items["): + match = re.match(r"items\[(\d+)\]\[(\w+)\]", key) + if match: + index, field = match.groups() + items[int(index)][field] = request.FILES.get(key) + + items_list = [items[idx] for idx in sorted(items.keys())] + data["items"] = items_list + + serializer = CreateProposalSerializer(data=data) + print("Cleaned data going to serializer:") + print(data) + if serializer.is_valid(): + proposal = serializer.save() + if request_instance.activeProposal is None: + Requests.objects.filter(id=request_id).update( + activeProposal=proposal.id, + status="Proposal created", + iwdAdminApproval=0, + directorApproval=0, + ) + else: + Requests.objects.filter(id=request_id).update( + activeProposal=proposal.id + ) + total_budget = 0 + for item_data in items_list: + try: + print("\n\n\n",item_data) + quantity = Decimal(item_data['quantity']) + price_per_unit = Decimal(item_data['price_per_unit']) + total_price = quantity * price_per_unit + item_data['total_price'] = total_price + total_budget += total_price + + newitem = Item.objects.create( + proposal=proposal, + name=item_data['name'], + description=item_data['description'], + unit=item_data['unit'], + quantity=quantity, + price_per_unit=price_per_unit, + total_price=quantity * price_per_unit + ) + if item_data['docs'] is not None: + newitem.docs.save(item_data['docs'].name, item_data['docs'], save=True) + except KeyError as e: + print(f"Error processing item {item_data}: {e}") + continue + proposal.proposal_budget = total_budget + proposal.save() + receiver_user_obj = User.objects.get(username=receiver_user) + iwd_notif(request.user, receiver_user_obj, "Proposal_added") + file_obj = File.objects.get(src_object_id=request_id, src_module="IWD") + if file_obj: + forward_file( + file_id=file_obj.id, + receiver=receiver_user, + receiver_designation=receiver_desg, + file_extra_JSON={"message": "Request forwarded."}, + remarks="updated proposal created", + ) + else: + return Response({"message":"file doesnot exist"}, status = status.HTTP_400_BAD_REQUEST) + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def director_approved_requests(request): + + ''' + requests approved by director and can issue work order + ''' + + requestsObject = Requests.objects.filter(directorApproval=1, issuedWorkOrder=0) + serializer = DirectorApprovedRequestsSerializer(requestsObject, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def issue_work_order(request): + ''' + issue work order + ''' + data = request.data.copy() + data['work_issuer'] = request.user.username + request_id = data.get('request_id') + request_instance = get_object_or_404(Requests, pk=request_id) + active_proposal = request_instance.activeProposal + proposal_obj = get_object_or_404(Proposal, pk=active_proposal) + data['estimate_budget']=proposal_obj.proposal_budget + print(data) + serializer = WorkOrderFormSerializer(data=data) + if serializer.is_valid(): + + work_order = serializer.save(request_id=request_instance) + + request_instance.status = "Work Order issued" + request_instance.issuedWorkOrder = 1 + request_instance.save() + + messages.success(request, "Work Order Issued") + return Response(status=status.HTTP_200_OK) + print("wow") + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def add_vendor(request): + ''' + add vendor for a particular work + ''' + data = request.data.copy() + serializer = VendorSerializer(data=data) + print("test 1\n\n\n\n\n") + print(serializer) + if serializer.is_valid(): + print("test 2\n\n\n\n\n") + serializer.save() + print("test 3\n\n\n\n\n") + messages.success(request, "Vendor Added") + return Response(status=status.HTTP_200_OK) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def work_under_progress(request): + + ''' + This api is used to get all requests under progress + ''' + + obj = [] + requestsObject = Requests.objects.filter(issuedWorkOrder=1, workCompleted=0) + serializer = WorkUnderProgressSerializer(requestsObject, many=True) + for result in serializer.data: + src_object_id = result['id'] + file_obj = File.objects.get(src_object_id=src_object_id, src_module="IWD") + if file_obj: + element = { + 'id': result['id'], + 'file_id': file_obj.id, + 'name': result['name'], + 'area': result['area'], + 'description': result['description'], + 'issuedWorkOrder': result['issuedWorkOrder'], + 'workCompleted': result['workCompleted'], + 'requestCreatedBy': result['requestCreatedBy'] + } + obj.append(element) + + return Response(obj, status=status.HTTP_200_OK) + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def requests_in_progress(request): + + ''' + work order issued but not completed + ''' + + requestsObject = Requests.objects.filter(issuedWorkOrder=1) + serializer = RequestsInProgressSerializer(requestsObject, many=True) + return Response(serializer.data, status=200) + + + +@api_view(['PATCH']) +@permission_classes([IsAuthenticated]) +def work_completed(request): + + ''' + to mark the work as completed + ''' + + request_id = request.data.get('id') + Requests.objects.filter(id=request_id).update(workCompleted=1, status="Work Completed") + return Response( + { + 'message': 'Work Completed', + }, + status=status.HTTP_200_OK + ) + + + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def view_budget(request): + + ''' + view budget list + ''' + + budget_objects = Budget.objects.all() + obj = [] + + for x in budget_objects: + element = { + "id": x.id, + "name": x.name, + "budgetIssued": x.budgetIssued + } + obj.append(element) + + return Response({'obj': obj}, status=status.HTTP_200_OK) + + + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def add_budget(request): + ''' + add new budget + ''' + name = request.data.get('name') + budget_issued = request.data.get('budget') + + if name and budget_issued: + formObject = Budget(name=name, budgetIssued=budget_issued) + formObject.save() + return Response({'message': 'Budget added successfully.'}, status=status.HTTP_201_CREATED) + else: + return Response({'error': 'Name and budget are required.'}, status=status.HTTP_400_BAD_REQUEST) + + + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def edit_budget(request): + + ''' + edit an existing budget + ''' + + budget_id = request.data.get('id') + budget_name = request.data.get('name') + budget_issued = request.data.get('budget') + + if budget_id and budget_name and budget_issued: + Budget.objects.filter(id=budget_id).update(name=budget_name, budgetIssued=budget_issued) + return Response({'message': 'Budget updated successfully.'}, status=status.HTTP_200_OK) + else: + return Response({'error': 'ID, name, and budget are required.'}, status=status.HTTP_400_BAD_REQUEST) + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def requests_status(request): + + ''' + this api will get status of all the requests in outbox of user + ''' + params = request.query_params + desg = params.get('role') + files = Requests.objects.all() + obj = [] + for request_object in files: + file_obj = File.objects.filter(src_object_id=request_object.id, src_module="IWD").first() + if request_object: + element = { + 'request_id': request_object.id, + 'name': request_object.name, + 'area': request_object.area, + 'description': request_object.description, + 'requestCreatedBy': request_object.requestCreatedBy, + 'file_id': file_obj.id, + 'processed_by_admin': request_object.iwdAdminApproval, + 'processed_by_director': request_object.directorApproval, + 'work_order': request_object.issuedWorkOrder, + 'work_completed': request_object.workCompleted, + 'processed_by_dean': request_object.deanProcessed, + 'status': request_object.status, + 'active_proposal': request_object.activeProposal, + 'creatiion_time' : request_object.creationTime, + } + obj.append(element) + return Response(obj, status=200) + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def get_work(request): + + ''' + this api is for fetching the selected work object + ''' + request_id = request.query_params.get("request_id") + print(request.query_params) + print(request_id) + work_obj = get_object_or_404(WorkOrder, request_id_id=request_id) + data = { + "id" : work_obj.id, + "request_id": request_id, + } + return Response(data, status=200) + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def get_vendors(request): + + ''' + this api is for fetching the selected work object + ''' + work = request.query_params.get("work") + vendors = Vendor.objects.filter(work=work) + data = [] + for vendor_obj in vendors: + object = { + "vendor_id": vendor_obj.id, + "name": vendor_obj.name, + "contact_number": vendor_obj.contact_number, + "email_address": vendor_obj.email_address, + } + data.append(object) + return Response(data, status=200) + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def get_issued_work(request): + + ''' + this api will get details of all the issued work orders + ''' + + params = request.query_params + desg = params.get('role') + files = Requests.objects.filter(issuedWorkOrder=1) + obj = [] + for request_object in files: + work_obj = WorkOrder.objects.filter(request_id=request_object.id).first() + if work_obj: + file_obj = File.objects.filter(src_object_id=request_object.id, src_module="IWD").first() + element = { + 'request_id': request_object.id, + 'name': request_object.name, + 'area': request_object.area, + 'description': request_object.description, + 'work_issuer': work_obj.work_issuer, + 'start_date': work_obj.start_date, + 'estimate_budget': work_obj.estimate_budget, + 'file_id': file_obj.id, + 'work_completed': request_object.workCompleted, + 'active_proposal': request_object.activeProposal, + 'processed_by_admin': request_object.iwdAdminApproval, + 'processed_by_director': request_object.directorApproval, + 'work_order': request_object.issuedWorkOrder, + } + obj.append(element) + return Response(obj, status=200) + + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def audit_document_view(request): + + ''' + This api is used to get a list of all the bills those are required to be audited + ''' + + params = request.query_params + desg = params.get('role') + if not desg: + return Response({"error": "Designation not provided"}, status=status.HTTP_400_BAD_REQUEST) + + inbox_files = view_inbox(username=request.user, designation=desg, src_module="IWD") + + obj = [] + for x in inbox_files: + try: + bill = Bills.objects.get(request_id=x['src_object_id']) # Efficient single query + file_obj = File.objects.get(src_object_id=x['src_object_id'], src_module="IWD") # Ensure this object exists + obj.append({ + 'request_id': x['src_object_id'], + 'file': bill.file, + 'fileUrl': bill.file.url, + 'file_id': file_obj.id + }) + except Bills.DoesNotExist: + print('bill with request_id ', x['src_object_id'], " not found") + except File.DoesNotExist: + print('file with request_id ', x['src_object_id'], " not found") + + return Response(obj, status=status.HTTP_200_OK) + + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def handle_process_bills(request): + + ''' + This api is used to submit (process) a bill + ''' + + obj = request.data + + fileid = obj.get('fileid') + try: + request_id = File.objects.get(id=fileid).src_object_id + except ObjectDoesNotExist: + return Response({'error': 'File not found.'}, status=status.HTTP_404_NOT_FOUND) + + remarks = obj.get('remarks') + attachment = request.FILES.get('attachment') + receiver_desg, receiver_user = obj['designation'].split('|') + + forward_file( + file_id=fileid, + receiver=receiver_user, + receiver_designation=receiver_desg, + file_extra_JSON={"message": "Request forwarded."}, + remarks=remarks, + file_attachment=attachment, + ) + + Requests.objects.filter(id=request_id).update(billProcessed=1, status="Final Bill Processed") + + request_instance = Requests.objects.get(pk=request_id) + + formObject = Bills() + formObject.request_id = request_instance + formObject.file = attachment + formObject.save() + receiver_user_obj = User.objects.get(username=receiver_user) + iwd_notif(request.user, receiver_user_obj, "file_forward") + + return Response({'obj': obj}, status=status.HTTP_200_OK) + +designations_list = ["Junior Engineer", "Executive Engineer (Civil)", "Electrical_AE", "Electrical_JE", "EE", "Civil_AE", "Civil_JE", "Dean (P&D)", "Director", "Accounts Admin", "Admin IWD", "Auditor"] + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def engineer_processed_requests(request): + obj = [] + desg = request.session.get('currentDesignationSelected') + + inbox_files = view_inbox( + username=request.user.username, + designation=desg, + src_module="IWD" + ) + + for result in inbox_files: + src_object_id = result['src_object_id'] + request_object = Requests.objects.filter(id=src_object_id).first() + file_obj = File.objects.get(src_object_id=src_object_id, src_module="IWD") + if request_object: + element = { + 'id': request_object.id, + 'name': request_object.name, + 'area': request_object.area, + 'description': request_object.description, + 'requestCreatedBy': request_object.requestCreatedBy, + 'file_id': file_obj.id + } + obj.append(element) + + return Response(obj) + + +# @api_view(['POST']) +# @permission_classes([IsAuthenticated]) +# def generateFinalBill(request): +# request_id = request.data.get("id", 0) + +# # Fetch the related work order +# work_order = WorkOrder.objects.get(request_id=request_id) + +# # Fetch IWD items +# iwd_items = StockItem.objects.filter(department=34) + +# items_list = [] + +# # Collecting items related to the request +# for x in iwd_items: +# stock_entry_id = x.StockEntryId.item_id.file_info +# indent_file_objects = IndentFile.objects.filter(file_info=stock_entry_id) +# for item in indent_file_objects: +# if item.purpose == request_id: +# element = [item.item_name, item.quantity, item.estimated_cost, item.file_info.upload_date] +# items_list.append(element) + +# filename = f"Request_id_{request_id}_final_bill.pdf" + +# buffer = BytesIO() +# c = canvas.Canvas(buffer, pagesize=letter) +# c.setFont("Helvetica", 12) + +# y_position = 750 +# rid = f"Request Id : {request_id}" +# agency = f"Agency : {work_order.agency}" + +# c.drawString(100, y_position, rid) +# y_position -= 20 +# c.drawString(100, y_position, agency) +# y_position -= 20 +# c.drawString(100, y_position - 40, "Items:") + +# # Prepare data for the table +# data = [["Item Name", "Quantity", "Cost (in Rupees)", "Date of Purchase", "Total Amount"]] +# for item in items_list: +# data.append([item[0], str(item[1]), "{:.2f}".format(item[2]), item[3], "{:.2f}".format(item[1] * item[2])]) + +# total_amount_to_be_paid = sum(item[1] * item[2] for item in items_list) +# c.drawString(100, y_position - 80, f"Total Amount (in Rupees): {total_amount_to_be_paid:.2f}") + +# # Create a table for the PDF +# table = Table(data) +# table.setStyle(TableStyle([('BACKGROUND', (0, 0), (-1, 0), colors.grey), +# ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke), +# ('ALIGN', (0, 0), (-1, -1), 'CENTER'), +# ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), +# ('BOTTOMPADDING', (0, 0), (-1, 0), 12), +# ('BACKGROUND', (0, 1), (-1, -1), colors.beige), +# ('GRID', (0, 0), (-1, -1), 1, colors.black)])) + +# table.wrapOn(c, 400, 600) +# table.drawOn(c, 100, y_position - 60) +# c.save() + +# buffer.seek(0) + +# response = HttpResponse(content_type='application/pdf') +# response['Content-Disposition'] = f'attachment; filename="{filename}"' +# response.write(buffer.getvalue()) + +# return response + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def handleBillGeneratedRequests(request): + request_id = request.data.get("id", 0) + if request_id: + Requests.objects.filter(id=request_id).update(status="Bill Generated", billGenerated=1) + + requests_object = Requests.objects.filter(issuedWorkOrder=1, billGenerated=0) + obj = [] + for x in requests_object: + element = { + "id": x.id, + "name": x.name, + "area": x.area, + "description": x.description, + "requestCreatedBy": x.requestCreatedBy, + "workCompleted": x.workCompleted, + } + obj.append(element) + + return Response({'obj': obj}, status=status.HTTP_200_OK) + + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def generatedBillsView(request): + request_objects = Requests.objects.filter(billGenerated=1) + obj = [] + for x in request_objects: + try: + file_obj = File.objects.get(src_object_id=x.id, src_module="IWD") + element = { + "id": x.id, + "name": x.name, + "description": x.description, + "area": x.area, + "requestCreatedBy": x.requestCreatedBy, + "file_id": file_obj.id, + } + obj.append(element) + except File.DoesNotExist: + continue + + return Response({'obj': obj}, status=status.HTTP_200_OK) + + + + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def settle_bills_view(request): + desg = request.session.get('currentDesignationSelected') + inbox_files = view_inbox(username=request.user, designation=desg, src_module="IWD") + + obj = [ + { + 'requestId': x['src_object_id'], + 'file': Bills.objects.get(request_id=x['src_object_id']).file, + 'fileUrl': Bills.objects.get(request_id=x['src_object_id']).file.url, + 'billSettled': Requests.objects.get(id=x['src_object_id']).billSettled, + 'fileId': File.objects.get(src_object_id=x['src_object_id'], src_module="IWD").id + } + for x in inbox_files + ] + + return Response({'data': obj}, status=status.HTTP_200_OK) + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def handle_settle_bill_requests(request): + request_id = request.data.get('id') + if request_id: + Requests.objects.filter(id=request_id).update(status="Final Bill Settled", billSettled=1) + + desg = request.session.get('currentDesignationSelected') + inbox_files = view_inbox(username=request.user, designation=desg, src_module="IWD") + + obj = [ + { + 'requestId': x['src_object_id'], + 'file': Bills.objects.get(request_id=x['src_object_id']).file, + 'fileUrl': Bills.objects.get(request_id=x['src_object_id']).file.url, + 'billSettled': Requests.objects.get(id=x['src_object_id']).billSettled, + 'fileId': File.objects.get(src_object_id=x['src_object_id'], src_module="IWD").id + } + for x in inbox_files + ] + + return Response({'message': "Final Bill settled", 'data': obj}, status=status.HTTP_200_OK) + + return Response({'error': 'Request ID not provided'}, status=status.HTTP_400_BAD_REQUEST) + + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def create_proposal(request): + data = request.data.copy() + request_id = data.get("id") + + request_instance = Requests.objects.filter(id=request_id, iwdAdminApproval=True).first() + if not request_instance: + return Response({'error': 'Request not approved by IWD Admin'}, status=status.HTTP_400_BAD_REQUEST) + + # Extract user and request info + receiver_desg, receiver_user = data.get("designation").split('|') + data["created_by"] = str(request.user) + data["request"] = request_id + + # Extract supporting docs if present + if request.FILES.get("supporting_documents"): + data["supporting_documents"] = request.FILES["supporting_documents"] + + # Parse items[] from FormData + items = defaultdict(dict) + for key in request.data: + if key.startswith("items["): + # key pattern: items[0][name] + import re + match = re.match(r"items\[(\d+)\]\[(\w+)\]", key) + if match: + index, field = match.groups() + value = request.data[key] + if field in ['quantity', 'price_per_unit']: # Cast numbers + try: + value = Decimal(value) + except: + pass + items[int(index)][field] = value + + # Handle file fields + for key in request.FILES: + if key.startswith("items["): + match = re.match(r"items\[(\d+)\]\[(\w+)\]", key) + if match: + index, field = match.groups() + items[int(index)][field] = request.FILES.get(key) + + # Flatten items to list + items_list = [items[idx] for idx in sorted(items.keys())] + data["items"] = items_list + + serializer = CreateProposalSerializer(data=data) + print("Cleaned data going to serializer:") + print(data) + if serializer.is_valid(): + proposal = serializer.save() + if request_instance.activeProposal is None: + Requests.objects.filter(id=request_id).update( + activeProposal=proposal.id, + status="Proposal created" + ) + else: + Requests.objects.filter(id=request_id).update( + activeProposal=proposal.id + ) + total_budget = 0 + for item_data in items_list: + try: + print("\n\n\n",item_data) + quantity = Decimal(item_data['quantity']) + price_per_unit = Decimal(item_data['price_per_unit']) + total_price = quantity * price_per_unit + item_data['total_price'] = total_price + total_budget += total_price + + # Create an Item instance for each item + + newitem = Item.objects.create( + proposal=proposal, + name=item_data['name'], + description=item_data['description'], + unit=item_data['unit'], + quantity=quantity, + price_per_unit=price_per_unit, + total_price=quantity * price_per_unit + ) + if item_data['docs'] is not None: + newitem.docs.save(item_data['docs'].name, item_data['docs'], save=True) + except KeyError as e: + print(f"Error processing item {item_data}: {e}") + continue + proposal.proposal_budget = total_budget + proposal.save() + # Proposal.objects.filter(id=proposal.id).update(proposal_budget=total_budget) + receiver_user_obj = User.objects.get(username=receiver_user) + iwd_notif(request.user, receiver_user_obj, "Proposal_added") + return Response(serializer.data, status=status.HTTP_201_CREATED) + + print("\n\n\n errors : ", serializer.errors) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def get_proposals(request): + data = request.query_params + proposals = Proposal.objects.filter(request_id=data.get("request_id")) + serializer = ProposalSerializer(proposals, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def get_items(request): + try: + data = request.query_params + proposal = Proposal.objects.filter(id = data['proposal_id']).first() + items = Item.objects.filter(proposal=data['proposal_id']) + itemsdata = ItemsSerializer(items, many=True) + proposaldata = ProposalSerializer(proposal) + return Response({"itemsList": itemsdata.data, "proposal":proposaldata.data}, status=status.HTTP_200_OK) + except Proposal.DoesNotExist: + return Response({'error': 'Proposal not found'}, status=status.HTTP_404_NOT_FOUND) + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def handle_admin_approval(request): + """ + Approve or reject a request by the IWD Admin. + """ + data = request.data + action = data.get('action') + + fileid = data.get('fileid') + request_id = File.objects.get(id=fileid).src_object_id + + remarks = data.get('remarks') + attachment = request.FILES.get('file') + receiver_desg, receiver_user = data.get('designation').split('|') + if not fileid: + return Response({'error': 'File ID not provided'}, status=status.HTTP_400_BAD_REQUEST) + + forward_file( + file_id=fileid, + receiver=receiver_user, + receiver_designation=receiver_desg, + file_extra_JSON={"message": "Request forwarded."}, + remarks=remarks, + file_attachment=attachment, + ) + receiver_user_obj = get_object_or_404(User, username=receiver_user) + iwd_notif(request.user, receiver_user_obj, "file_forward") + message = "" + + if not request_id or not action: + return Response({'error': 'Request ID and action are required'}, status=status.HTTP_400_BAD_REQUEST) + + request_instance = Requests.objects.filter(id=request_id).first() + if not request_instance: + return Response({'error': 'Request not found'}, status=status.HTTP_404_NOT_FOUND) + + if action == "approve": + if request_instance.activeProposal: + Requests.objects.filter(id=request_id).update(iwdAdminApproval=1, status="Proposal created") + else: + Requests.objects.filter(id=request_id).update(iwdAdminApproval=1, status="Approved by the IWD Admin") + return Response({'message': 'Request approved by IWD Admin'}, status=status.HTTP_200_OK) + elif action == "reject": + Requests.objects.filter(id=request_id).update(iwdAdminApproval=-1, status="Rejected", activeProposal=None) + return Response({'message': 'Request rejected by IWD Admin'}, status=status.HTTP_200_OK) + else: + return Response({'error': 'Invalid action'}, status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file diff --git a/FusionIIIT/applications/iwdModuleV2/models.py b/FusionIIIT/applications/iwdModuleV2/models.py index a5c40c7b2..d5642960b 100644 --- a/FusionIIIT/applications/iwdModuleV2/models.py +++ b/FusionIIIT/applications/iwdModuleV2/models.py @@ -1,162 +1,129 @@ from django.db import models +from datetime import date +from django.contrib.auth.models import User +from applications.filetracking.models import File -# Create your models here. +class RequestStatus(models.TextChoices): + CREATED = "CREATED", "Created" + ENGINEER_PROCESSED = "ENGINEER_PROCESSED", "Engineer Processed" + ADMIN_APPROVED = "ADMIN_APPROVED", "Admin Approved" + DIRECTOR_APPROVED = "DIRECTOR_APPROVED", "Director Approved" + DEAN_PROCESSED = "DEAN_PROCESSED", "Dean Processed" + WORK_ORDER_ISSUED = "WORK_ORDER_ISSUED", "Work Order Issued" + WORK_COMPLETED = "WORK_COMPLETED", "Work Completed" + BILL_GENERATED = "BILL_GENERATED", "Bill Generated" + BILL_PROCESSED = "BILL_PROCESSED", "Bill Processed" + BILL_SETTLED = "BILL_SETTLED", "Bill Settled" -class Projects(models.Model): - id = models.CharField(primary_key=True, max_length=200) +class ProposalStatus(models.TextChoices): + PENDING = "Pending", "Pending" + APPROVED = "Approved", "Approved" + REJECTED = "Rejected", "Rejected" -class PageOneDetails(models.Model): - id = models.ForeignKey(Projects, on_delete=models.CASCADE, primary_key=True) - aESFile = models.FileField(null=True) - dASA = models.DateField(null=True) - nitNiqNo = models.IntegerField(null=True) - proTh = models.CharField(null=True, max_length=200) - emdDetails = models.CharField(null=True, max_length=200) - preBidDate = models.DateField(null=True, max_length=200) - technicalBidDate = models.DateField(null=True) - financialBidDate = models.DateField(null=True) +class BillType(models.IntegerChoices): + PARTIAL = 0, "Partial" + FINAL = 1, "Final" -class AESDetails(models.Model): - key = models.ForeignKey(Projects, on_delete=models.CASCADE) - sNo = models.CharField(max_length=100) - descOfItems = models.CharField(max_length=200) - unit = models.CharField(max_length=200) - quantity = models.IntegerField() - rate = models.IntegerField() - amount = models.IntegerField() +class BaseModel(models.Model): + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + is_active = models.BooleanField(default=True) + class Meta: + abstract = True -class PageTwoDetails(models.Model): - id = models.ForeignKey(Projects, on_delete=models.CASCADE, primary_key=True) - corrigendum = models.FileField(null=True) - addendum = models.FileField(null=True) - preBidMeetingDetails = models.FileField(null=True) - technicalBidMeetingDetails = models.FileField(null=True) - technicallyQualifiedAgencies = models.CharField(null=True, max_length=200) - financialBidMeetingDetails = models.FileField(null=True) - nameOfLowestAgency = models.CharField(null=True, max_length=200) - letterOfIntent = models.FileField(null=True) - workOrder = models.FileField(null=True) - agreementLetter = models.FileField(null=True) - milestones = models.FileField(null=True) + def delete(self, *args, **kwargs): + self.is_active = False + self.save() -class CorrigendumTable(models.Model): - key = models.ForeignKey(Projects, on_delete=models.CASCADE, unique=True) - issueDate = models.DateField() - nitNo = models.IntegerField() +class Request(BaseModel): name = models.CharField(max_length=200) - lastDate = models.DateField(null=True) - lastTime = models.TimeField() - env1BidOpeningDate = models.DateField() - env1BidOpeningTime = models.TimeField() - env2BidOpeningDate = models.DateField() - env2BidOpeningTime = models.TimeField() - - -class Addendum(models.Model): - key = models.ForeignKey(Projects, on_delete=models.CASCADE, unique=True) - issueDate = models.DateField() - nitNiqNo = models.IntegerField() - name = models.CharField(max_length=200) - openDate = models.DateField() - openTime = models.TimeField() - - -class PreBidDetails(models.Model): - key = models.ForeignKey(Projects, on_delete=models.CASCADE, unique=True) - sNo = models.CharField(max_length=200) - nameOfParticipants = models.CharField(max_length=200) - issuesRaised = models.CharField(max_length=200) - responseDecision = models.CharField(max_length=200) - - -class TechnicalBidDetails(models.Model): - key = models.ForeignKey(Projects, on_delete=models.CASCADE, unique=True) - sNo = models.CharField(max_length=200) - requirements = models.CharField(max_length=200) - + description = models.CharField(max_length=1000) + area = models.CharField(max_length=200) + requestCreatedBy = models.ForeignKey(User, on_delete=models.CASCADE, related_name="created_requests", db_index=True) + status = models.CharField(max_length=50, choices=RequestStatus.choices, default=RequestStatus.CREATED) + activeProposal = models.IntegerField(null=True, blank=True) -class TechnicalBidContractorDetails(models.Model): - key = models.ForeignKey(TechnicalBidDetails, on_delete=models.CASCADE) - name = models.CharField(max_length=200) - description = models.CharField(max_length=200) - - -class FinancialBidDetails(models.Model): - key = models.ForeignKey(Projects, on_delete=models.CASCADE, unique=True) - sNo = models.CharField(max_length=200) - description = models.CharField(max_length=200) - -class FinancialContractorDetails(models.Model): - key = models.ForeignKey(FinancialBidDetails, on_delete=models.CASCADE) +class WorkOrder(BaseModel): + request = models.ForeignKey(Request, on_delete=models.CASCADE, db_index=True) name = models.CharField(max_length=200) - estimatedCost = models.IntegerField() - percentageRelCost = models.IntegerField() - perFigures = models.IntegerField() - totalCost = models.IntegerField() + date = models.DateField(default=date.today) + estimate_budget = models.DecimalField(default=0, max_digits=10, decimal_places=2) + alloted_time = models.CharField(max_length=200) + start_date = models.DateField() + completion_date = models.DateField(null=True, blank=True) + work_issuer = models.CharField(max_length=200) + amount_spent = models.DecimalField(default=0, max_digits=10, decimal_places=2) -class LetterOfIntentDetails(models.Model): - key = models.ForeignKey(Projects, on_delete=models.CASCADE, unique=True) - nitNiqNo = models.IntegerField() - dateOfOpening = models.DateField() - agency = models.CharField(max_length=200) +class Vendor(BaseModel): + work = models.ForeignKey(WorkOrder, on_delete=models.CASCADE, db_index=True) name = models.CharField(max_length=200) - tenderValue = models.IntegerField() - - -class WorkOrderForm(models.Model): - key = models.ForeignKey(Projects, on_delete=models.CASCADE, unique=True) - issueDate = models.DateField() - nitNiqNo = models.IntegerField() - agency = models.CharField(max_length=200) + itemdata = models.FileField(null=True, blank=True, upload_to='iwd/vendors/') + finalbill = models.BooleanField(default=False) + total_amount = models.DecimalField(default=0, max_digits=10, decimal_places=2) + contact_number = models.CharField(max_length=20, blank=True, null=True) + email_address = models.CharField(null=True, blank=True, max_length=200) + + class Meta: + constraints = [ + models.UniqueConstraint(fields=["work", "name"], name="unique_vendor_per_work") + ] + + +class Bills(BaseModel): + vendor = models.ForeignKey(Vendor, on_delete=models.CASCADE, db_index=True) + file = models.FileField(upload_to='iwd/bills/', null=True, blank=True) + audit = models.BooleanField(default=False) + settle = models.BooleanField(default=False) + total_amount = models.DecimalField(default=0, max_digits=10, decimal_places=2) + billtype = models.IntegerField(choices=BillType.choices, default=BillType.PARTIAL) + + def clean(self): + if self.billtype == BillType.FINAL: + exists = Bills.objects.filter(vendor=self.vendor, billtype=BillType.FINAL).exclude(id=self.id).exists() + if exists: + raise ValueError("Final bill already exists for this vendor.") + + +class BillItems(BaseModel): + bill = models.ForeignKey(Bills, on_delete=models.CASCADE, db_index=True) + name = models.CharField(max_length=100) + description = models.CharField(max_length=100) + quantity = models.IntegerField(default=0) + price = models.DecimalField(default=0, max_digits=10, decimal_places=2) + + +class Budget(BaseModel): + request = models.ForeignKey(Request, on_delete=models.CASCADE, db_index=True) name = models.CharField(max_length=200) - amount = models.IntegerField() - time = models.IntegerField() - monthDay = models.IntegerField() - startDate = models.DateField() - completionDate = models.DateField() - deposit = models.IntegerField() - contractDay = models.IntegerField() - - -class Agreement(models.Model): - key = models.ForeignKey(Projects, on_delete=models.CASCADE, unique=True) - date = models.DateField() - agencyName = models.CharField(max_length=200) - workName = models.CharField(max_length=200) - fdrSum = models.IntegerField() - - -class Milestones(models.Model): - key = models.ForeignKey(Projects, on_delete=models.CASCADE) - sNo = models.CharField(max_length=200) - description = models.CharField(max_length=200) - timeAllowed = models.IntegerField() - amountWithheld = models.IntegerField() - - -class PageThreeDetails(models.Model): - id = models.ForeignKey(Projects, on_delete=models.CASCADE, primary_key=True) - extensionOfTime = models.FileField() - actualCostOfBuilding = models.IntegerField() - - -class ExtensionOfTimeDetails(models.Model): - key = models.ForeignKey(Projects, on_delete=models.CASCADE) - sNo = models.CharField(max_length=200) - hindrance = models.CharField(max_length=200) - periodOfHindrance = models.IntegerField() - periodOfExtension = models.IntegerField() - - -class NoOfTechnicalBidTimes(models.Model): - key = models.ForeignKey(Projects, on_delete=models.CASCADE, unique=True) - number = models.IntegerField() - + budgetIssued = models.BooleanField(default=False) + + +class Proposal(BaseModel): + request = models.ForeignKey(Request, on_delete=models.CASCADE, related_name='proposals', db_index=True) + created_by = models.ForeignKey(User, on_delete=models.CASCADE) + proposal_budget = models.DecimalField(max_digits=15, decimal_places=2, null=True, blank=True) + supporting_documents = models.FileField(upload_to='iwd/proposals/', null=True, blank=True) + status = models.CharField(max_length=20, choices=ProposalStatus.choices, default=ProposalStatus.PENDING) + + +class Item(BaseModel): + proposal = models.ForeignKey('Proposal', on_delete=models.CASCADE, related_name='items', db_index=True) + name = models.CharField(default=" ", max_length=255) + description = models.TextField(default=" ") + unit = models.CharField(default=" ", max_length=50) + price_per_unit = models.DecimalField(default=0, max_digits=10, decimal_places=2) + quantity = models.IntegerField(default=0) + total_price = models.DecimalField(default=0, max_digits=10, decimal_places=2) + docs = models.FileField(upload_to='iwd/items/', null=True, blank=True) + + def save(self, *args, **kwargs): + self.total_price = self.price_per_unit * self.quantity + super().save(*args, **kwargs) \ No newline at end of file diff --git a/FusionIIIT/applications/iwdModuleV2/urls.py b/FusionIIIT/applications/iwdModuleV2/urls.py index 6ad401098..270877b87 100644 --- a/FusionIIIT/applications/iwdModuleV2/urls.py +++ b/FusionIIIT/applications/iwdModuleV2/urls.py @@ -1,40 +1,37 @@ -from django.conf.urls import url - +from django.urls import path, include from . import views -app_name = 'iwdModuleV2' +app_name = "iwdModuleV2" urlpatterns = [ + # Dashboard + path("", views.dashboard, name="dashboard"), + + # API Versioning + path("api/v1/iwd/", include("applications.iwdModuleV2.api.urls")), + + # Web views + path("requests/", views.requestsView, name="requests"), + path("created-requests/", views.createdRequests, name="created-requests"), + + path("engineer-processed-requests/", views.engineerProcessedRequests, name="engineer-processed-requests"), + path("dean-processed-requests/", views.deanProcessedRequests, name="dean-processed-requests"), + + path("rejected-requests/", views.rejectedRequests, name="rejected-requests"), + + path("requests-status/", views.requestsStatus, name="requests-status"), + + path("work-orders/", views.workOrder, name="work-orders"), + path("issue-work-order/", views.issueWorkOrder, name="issue-work-order"), + + path("requests-in-progress/", views.requestsInProgess, name="requests-in-progress"), + path("work-completed/", views.workCompleted, name="work-completed"), + + path("budget/", views.budget, name="budget"), + path("budget/view/", views.viewBudget, name="view-budget"), + path("budget/add/", views.addBudget, name="add-budget"), + path("budget/edit/", views.editBudget, name="edit-budget"), - url(r'^$', views.dashboard, name='IWD Dashboard'), - url(r'^page1_1/$', views.page1_1, name='IWD Page1.1'), - url(r'page2_1/$', views.page2_1, name='IWD Page2.1'), - url(r'corrigendumInput/$', views.corrigendumInput, name='Corrigendum Input'), - url(r'addendumInput/$', views.addendumInput, name='Addendum Input'), - url(r'milestoneForm/$', views.milestonesForm, name='Milestone Form'), - url(r'technicalBidForm/$', views.TechnicalBidForm, name='Technical Bid Form'), - url(r'extensionForm/$', views.ExtensionOfTimeForm, name='Extension Form'), - url(r'letterOfIntent/$', views.letterOfIntent, name='Letter Of Intent Input'), - url(r'workOrderForm/$', views.workOrderForm, name='Work Order Form'), - url(r'agreement/$', views.AgreementInput, name='Agreement Input'), - url(r'page3_1/$', views.page3_1, name='IWD Page 3.1'), - url(r'noOfEntriesTechnicalBid/$', views.noOfEntriesTechnicalBid, name='IWD Technical Bid'), - url(r'noOfEntriesFinancialBid/$', views.noOfEntriesFinancialBid, name='IWD Financial Bid'), - url(r'page1View/$', views.page1View, name='Page 1 Views'), - url(r'page2View/$', views.page2View, name='Page 2 View'), - url(r'page3View/$', views.page3View, name='Page 3 View'), - url(r'extensionFormView/$', views.extensionFormView, name='Extension Form'), - url(r'AESView/$', views.AESView, name='AES View'), - url(r'financialBidView/$', views.financialBidView, name='Financial Bid View'), - url(r'preBidForm/$', views.PreBidForm, name='Pre Bid Form'), - url(r'AESForm/$', views.AESForm, name='AESForm'), - url('workOrderFormView/$', views.workOrderFormView, name='Work Order Form View'), - url(r'letterOfIntentView', views.letterOfIntentView, name='Letter Of Intent View'), - url(r'preBidDetailsView/$', views.preBidDetailsView, name='Pre Bid Details View'), - url(r'technicalBidView/$', views.technicalBidView, name='Technical Bid View'), - url(r'milestoneView/$', views.milestoneView, name='Milestones'), - url(r'addendumView/$', views.addendumView, name='Addendum View'), - url('agreementView/$', views.agreementView, name='Agreement VIew'), - url(r'corrigendumView/$', views.corrigendumView, name='Corrigendum View') -] + path("files///", views.view_file, name="view-file"), +] \ No newline at end of file