added vehicles and vehicles extra, dynamic tables for entities, dynamic dropdowns with overlay for selecting and manage data

This commit is contained in:
2026-02-10 17:32:33 +02:00
parent ed35a90cc0
commit 6a42099169
60 changed files with 4845 additions and 81 deletions
+168 -11
View File
@@ -2,10 +2,16 @@ from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, AllowAny
from .models import ComPortReading, User
from .serializers import ComPortReadingSerializer, UserSerializer, UserDetailSerializer, ChangePasswordSerializer, VehicleSerializer, NomenclatureSerializer
from .models import ComPortReading, User, Report
from .serializers import (
ComPortReadingSerializer, UserSerializer, UserDetailSerializer,
ChangePasswordSerializer, VehicleSerializer, NomenclatureSerializer,
NomenclatureEntrySerializer, ReportSerializer,
)
from django.utils import timezone
from vehicles.models import Vehicle
from nomenclatures.models import Nomenclature
from nomenclatures.models import Nomenclature, NomenclatureEntry
from scalesapp.sse import sse_broadcast_update
class UserViewSet(viewsets.ModelViewSet):
@@ -77,7 +83,7 @@ class ComPortReadingViewSet(viewsets.ModelViewSet):
serializer = self.get_serializer(reading)
return Response(serializer.data)
return Response({'detail': 'No readings yet'}, status=status.HTTP_404_NOT_FOUND)
@action(detail=False, methods=['get'])
def by_port(self, request):
"""Get readings for a specific port"""
@@ -90,7 +96,7 @@ class ComPortReadingViewSet(viewsets.ModelViewSet):
readings = ComPortReading.objects.filter(port=port)
serializer = self.get_serializer(readings, many=True)
return Response(serializer.data)
def get_client_ip(self, request):
"""Get client IP address"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
@@ -99,7 +105,7 @@ class ComPortReadingViewSet(viewsets.ModelViewSet):
else:
ip = request.META.get('REMOTE_ADDR')
return ip
def perform_create(self, serializer):
"""Save the reading with client IP"""
serializer.save(source_ip=self.get_client_ip(self.request))
@@ -116,14 +122,31 @@ class VehicleViewSet(viewsets.ModelViewSet):
partial_update: Partial update of vehicle
destroy: Delete a vehicle (cascades to VehicleExtra)
by_number: Get vehicle by vehicle_number
set_tare: Set tare weight from scale reading
set_gross: Set gross weight from scale reading
"""
queryset = Vehicle.objects.select_related('extra').all()
queryset = Vehicle.objects.select_related('extra', 'tare_user', 'gross_user').all()
serializer_class = VehicleSerializer
permission_classes = [IsAuthenticated]
filterset_fields = ['vehicle_number']
search_fields = ['vehicle_number']
ordering = ['vehicle_number']
def perform_create(self, serializer):
instance = serializer.save()
data = VehicleSerializer(instance).data
sse_broadcast_update('vehicle', 'created', data)
def perform_update(self, serializer):
instance = serializer.save()
data = VehicleSerializer(instance).data
sse_broadcast_update('vehicle', 'updated', data)
def perform_destroy(self, instance):
data = VehicleSerializer(instance).data
instance.delete()
sse_broadcast_update('vehicle', 'deleted', data)
@action(detail=False, methods=['get'], url_path='by-number')
def by_number(self, request):
"""Get vehicle by vehicle_number query parameter"""
@@ -144,6 +167,48 @@ class VehicleViewSet(viewsets.ModelViewSet):
status=status.HTTP_404_NOT_FOUND
)
@action(detail=True, methods=['post'], url_path='set-tare')
def set_tare(self, request, pk=None):
"""Set tare weight from scale reading"""
vehicle = self.get_object()
value = request.data.get('value')
if value is None:
return Response({'detail': 'value is required'}, status=status.HTTP_400_BAD_REQUEST)
try:
value = int(value)
except (ValueError, TypeError):
return Response({'detail': 'value must be a number'}, status=status.HTTP_400_BAD_REQUEST)
vehicle.tare = value
vehicle.tare_date = timezone.now()
vehicle.tare_user = request.user
vehicle.save()
serializer = self.get_serializer(vehicle)
sse_broadcast_update('vehicle', 'updated', serializer.data)
return Response(serializer.data)
@action(detail=True, methods=['post'], url_path='set-gross')
def set_gross(self, request, pk=None):
"""Set gross weight from scale reading"""
vehicle = self.get_object()
value = request.data.get('value')
if value is None:
return Response({'detail': 'value is required'}, status=status.HTTP_400_BAD_REQUEST)
try:
value = int(value)
except (ValueError, TypeError):
return Response({'detail': 'value must be a number'}, status=status.HTTP_400_BAD_REQUEST)
vehicle.gross = value
vehicle.gross_date = timezone.now()
vehicle.gross_user = request.user
vehicle.save()
serializer = self.get_serializer(vehicle)
sse_broadcast_update('vehicle', 'updated', serializer.data)
return Response(serializer.data)
class NomenclatureViewSet(viewsets.ModelViewSet):
"""
@@ -154,16 +219,30 @@ class NomenclatureViewSet(viewsets.ModelViewSet):
retrieve: Get a specific nomenclature by ID
update: Full update of nomenclature and fields
partial_update: Partial update of nomenclature
destroy: Delete a nomenclature (cascades to fields)
destroy: Delete a nomenclature (cascades to fields and entries)
by_code: Get nomenclature by code
by_applies_to: Filter nomenclatures by applies_to type
entries: List or create entries for a specific nomenclature
"""
queryset = Nomenclature.objects.prefetch_related('nomenclaturefield_set').all()
queryset = Nomenclature.objects.prefetch_related('fields').all()
serializer_class = NomenclatureSerializer
permission_classes = [IsAuthenticated]
filterset_fields = ['applies_to', 'code']
filterset_fields = ['applies_to', 'code', 'kind']
search_fields = ['code', 'name']
ordering = ['code']
ordering = ['sort_order', 'code']
def perform_create(self, serializer):
instance = serializer.save()
sse_broadcast_update('nomenclature', 'insert', NomenclatureSerializer(instance).data)
def perform_update(self, serializer):
instance = serializer.save()
sse_broadcast_update('nomenclature', 'update', NomenclatureSerializer(instance).data)
def perform_destroy(self, instance):
data = NomenclatureSerializer(instance).data
instance.delete()
sse_broadcast_update('nomenclature', 'delete', data)
@action(detail=False, methods=['get'], url_path='by-code')
def by_code(self, request):
@@ -209,3 +288,81 @@ class NomenclatureViewSet(viewsets.ModelViewSet):
serializer = self.get_serializer(nomenclatures, many=True)
return Response(serializer.data)
@action(detail=True, methods=['get', 'post'], url_path='entries')
def entries(self, request, pk=None):
"""List or create entries for a specific nomenclature."""
nomenclature = self.get_object()
if request.method == 'GET':
entries = nomenclature.entries.all()
is_active = request.query_params.get('is_active')
if is_active is not None:
entries = entries.filter(is_active=is_active.lower() == 'true')
serializer = NomenclatureEntrySerializer(entries, many=True)
return Response(serializer.data)
serializer = NomenclatureEntrySerializer(
data=request.data,
context={'nomenclature': nomenclature},
)
serializer.is_valid(raise_exception=True)
serializer.save(nomenclature=nomenclature)
entry_data = {**serializer.data, 'nomenclature_code': nomenclature.code}
sse_broadcast_update('nomenclature_entry', 'insert', entry_data)
return Response(serializer.data, status=status.HTTP_201_CREATED)
class NomenclatureEntryViewSet(viewsets.ModelViewSet):
"""
API endpoint for individual nomenclature entry management.
For list/create use the nested endpoint: /api/nomenclatures/{id}/entries/
This viewset handles retrieve/update/delete of individual entries.
"""
queryset = NomenclatureEntry.objects.select_related('nomenclature').all()
serializer_class = NomenclatureEntrySerializer
permission_classes = [IsAuthenticated]
def get_serializer_context(self):
context = super().get_serializer_context()
if self.kwargs.get('pk'):
try:
entry = NomenclatureEntry.objects.select_related('nomenclature').get(pk=self.kwargs['pk'])
context['nomenclature'] = entry.nomenclature
except NomenclatureEntry.DoesNotExist:
pass
return context
def perform_update(self, serializer):
instance = serializer.save()
entry_data = {**NomenclatureEntrySerializer(instance).data, 'nomenclature_code': instance.nomenclature.code}
sse_broadcast_update('nomenclature_entry', 'update', entry_data)
def perform_destroy(self, instance):
entry_data = {**NomenclatureEntrySerializer(instance).data, 'nomenclature_code': instance.nomenclature.code}
instance.delete()
sse_broadcast_update('nomenclature_entry', 'delete', entry_data)
class ReportViewSet(viewsets.ModelViewSet):
"""
API endpoint for report template management.
list: Get all reports
create: Create a new report
retrieve: Get a specific report by ID
update: Update a report
destroy: Delete a report
"""
queryset = Report.objects.select_related('created_by').all()
serializer_class = ReportSerializer
permission_classes = [IsAuthenticated]
filterset_fields = ['name']
search_fields = ['name']
ordering = ['-updated_at']
def perform_create(self, serializer):
serializer.save(created_by=self.request.user)
def perform_update(self, serializer):
serializer.save()