Initial commit
This commit is contained in:
367
gestion_pedidos/views.py
Normal file
367
gestion_pedidos/views.py
Normal file
@@ -0,0 +1,367 @@
|
||||
from rest_framework import viewsets, status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from django.db.models import Q, Prefetch
|
||||
from django.utils import timezone
|
||||
from django.shortcuts import render
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
import json
|
||||
|
||||
from .models import (
|
||||
Cliente, PedidoCliente, ReferenciaPedidoCliente,
|
||||
Proveedor, PedidoProveedor, ReferenciaPedidoProveedor,
|
||||
Albaran, ReferenciaAlbaran, Devolucion, StockReferencia
|
||||
)
|
||||
from .serializers import (
|
||||
ClienteSerializer, PedidoClienteSerializer, ReferenciaPedidoClienteSerializer,
|
||||
ProveedorSerializer, PedidoProveedorSerializer, ReferenciaPedidoProveedorSerializer,
|
||||
AlbaranSerializer, ReferenciaAlbaranSerializer, DevolucionSerializer,
|
||||
StockReferenciaSerializer, UpdateStockSerializer, BulkUpdateStockSerializer,
|
||||
CrearPedidoProveedorSerializer
|
||||
)
|
||||
from .services.albaran_processor import AlbaranProcessor
|
||||
from .services.pdf_parser import PDFPedidoParser
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class ClienteViewSet(viewsets.ModelViewSet):
|
||||
queryset = Cliente.objects.all()
|
||||
serializer_class = ClienteSerializer
|
||||
filterset_fields = ['nombre', 'matricula_vehiculo']
|
||||
|
||||
|
||||
class PedidoClienteViewSet(viewsets.ModelViewSet):
|
||||
queryset = PedidoCliente.objects.select_related('cliente').prefetch_related('referencias').all()
|
||||
serializer_class = PedidoClienteSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
|
||||
# Filtros
|
||||
estado = self.request.query_params.get('estado')
|
||||
if estado:
|
||||
queryset = queryset.filter(estado=estado)
|
||||
|
||||
urgente = self.request.query_params.get('urgente')
|
||||
if urgente == 'true':
|
||||
queryset = [p for p in queryset if p.es_urgente]
|
||||
|
||||
matricula = self.request.query_params.get('matricula')
|
||||
if matricula:
|
||||
queryset = queryset.filter(cliente__matricula_vehiculo__icontains=matricula)
|
||||
|
||||
return queryset
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def actualizar_estado(self, request, pk=None):
|
||||
"""Actualiza el estado del pedido"""
|
||||
pedido = self.get_object()
|
||||
nuevo_estado = request.data.get('estado')
|
||||
|
||||
if nuevo_estado in dict(PedidoCliente.ESTADOS):
|
||||
pedido.estado = nuevo_estado
|
||||
pedido.save()
|
||||
return Response({'status': 'Estado actualizado'})
|
||||
return Response({'error': 'Estado inválido'}, status=400)
|
||||
|
||||
|
||||
class ReferenciaPedidoClienteViewSet(viewsets.ModelViewSet):
|
||||
queryset = ReferenciaPedidoCliente.objects.all()
|
||||
serializer_class = ReferenciaPedidoClienteSerializer
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def marcar_stock(self, request, pk=None):
|
||||
"""Marca unidades en stock para una referencia"""
|
||||
referencia = self.get_object()
|
||||
unidades = request.data.get('unidades_en_stock', 0)
|
||||
|
||||
referencia.unidades_en_stock = max(0, unidades)
|
||||
referencia.save()
|
||||
|
||||
# Actualizar estado del pedido si es necesario
|
||||
pedido = referencia.pedido_cliente
|
||||
todas_completas = all(
|
||||
ref.unidades_pendientes == 0
|
||||
for ref in pedido.referencias.all()
|
||||
)
|
||||
|
||||
if todas_completas and pedido.estado != 'completado':
|
||||
pedido.estado = 'completado'
|
||||
pedido.save()
|
||||
elif pedido.estado == 'pendiente_revision':
|
||||
pedido.estado = 'en_revision'
|
||||
pedido.save()
|
||||
|
||||
return Response(ReferenciaPedidoClienteSerializer(referencia).data)
|
||||
|
||||
|
||||
class ProveedorViewSet(viewsets.ModelViewSet):
|
||||
queryset = Proveedor.objects.filter(activo=True)
|
||||
serializer_class = ProveedorSerializer
|
||||
|
||||
|
||||
class PedidoProveedorViewSet(viewsets.ModelViewSet):
|
||||
queryset = PedidoProveedor.objects.select_related('proveedor').prefetch_related('referencias').all()
|
||||
serializer_class = PedidoProveedorSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
proveedor_id = self.request.query_params.get('proveedor_id')
|
||||
if proveedor_id:
|
||||
queryset = queryset.filter(proveedor_id=proveedor_id)
|
||||
return queryset
|
||||
|
||||
@action(detail=False, methods=['post'])
|
||||
def crear_manual(self, request):
|
||||
"""Crea un pedido a proveedor manualmente"""
|
||||
serializer = CrearPedidoProveedorSerializer(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=400)
|
||||
|
||||
data = serializer.validated_data
|
||||
proveedor = Proveedor.objects.get(id=data['proveedor_id'])
|
||||
|
||||
pedido = PedidoProveedor.objects.create(
|
||||
proveedor=proveedor,
|
||||
numero_pedido=data.get('numero_pedido', ''),
|
||||
tipo=data['tipo'],
|
||||
estado='pendiente_recepcion'
|
||||
)
|
||||
|
||||
# Crear referencias
|
||||
for ref_data in data['referencias']:
|
||||
ref_pedido_cliente_id = ref_data.get('referencia_pedido_cliente_id')
|
||||
ref_pedido_cliente = None
|
||||
if ref_pedido_cliente_id:
|
||||
try:
|
||||
ref_pedido_cliente = ReferenciaPedidoCliente.objects.get(id=ref_pedido_cliente_id)
|
||||
except ReferenciaPedidoCliente.DoesNotExist:
|
||||
pass
|
||||
|
||||
ReferenciaPedidoProveedor.objects.create(
|
||||
pedido_proveedor=pedido,
|
||||
referencia_pedido_cliente=ref_pedido_cliente,
|
||||
referencia=ref_data['referencia'],
|
||||
denominacion=ref_data.get('denominacion', ''),
|
||||
unidades_pedidas=int(ref_data['unidades']),
|
||||
)
|
||||
|
||||
return Response(PedidoProveedorSerializer(pedido).data, status=201)
|
||||
|
||||
|
||||
class AlbaranViewSet(viewsets.ModelViewSet):
|
||||
queryset = Albaran.objects.select_related('proveedor').prefetch_related('referencias').all()
|
||||
serializer_class = AlbaranSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
estado = self.request.query_params.get('estado_procesado')
|
||||
if estado:
|
||||
queryset = queryset.filter(estado_procesado=estado)
|
||||
return queryset
|
||||
|
||||
@action(detail=False, methods=['post'])
|
||||
def upload(self, request):
|
||||
"""Sube un albarán desde móvil o web"""
|
||||
if 'archivo' not in request.FILES:
|
||||
return Response({'error': 'No se proporcionó archivo'}, status=400)
|
||||
|
||||
archivo = request.FILES['archivo']
|
||||
|
||||
# Guardar archivo
|
||||
upload_dir = settings.ALBARANES_ESCANEADOS_DIR
|
||||
upload_dir.mkdir(exist_ok=True)
|
||||
|
||||
file_path = upload_dir / archivo.name
|
||||
with open(file_path, 'wb+') as destination:
|
||||
for chunk in archivo.chunks():
|
||||
destination.write(chunk)
|
||||
|
||||
# Procesar
|
||||
try:
|
||||
processor = AlbaranProcessor()
|
||||
albaran = processor.process_albaran_file(file_path)
|
||||
return Response(AlbaranSerializer(albaran).data, status=201)
|
||||
except Exception as e:
|
||||
return Response({'error': str(e)}, status=400)
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def vincular_proveedor(self, request, pk=None):
|
||||
"""Vincula un albarán a un proveedor manualmente"""
|
||||
albaran = self.get_object()
|
||||
proveedor_id = request.data.get('proveedor_id')
|
||||
|
||||
if not proveedor_id:
|
||||
return Response({'error': 'proveedor_id requerido'}, status=400)
|
||||
|
||||
try:
|
||||
proveedor = Proveedor.objects.get(id=proveedor_id)
|
||||
albaran.proveedor = proveedor
|
||||
albaran.estado_procesado = 'procesado'
|
||||
albaran.save()
|
||||
|
||||
# Reprocesar para vincular referencias
|
||||
processor = AlbaranProcessor()
|
||||
processor._match_and_update_referencias(albaran)
|
||||
|
||||
return Response(AlbaranSerializer(albaran).data)
|
||||
except Proveedor.DoesNotExist:
|
||||
return Response({'error': 'Proveedor no encontrado'}, status=404)
|
||||
|
||||
|
||||
class DevolucionViewSet(viewsets.ModelViewSet):
|
||||
queryset = Devolucion.objects.select_related('proveedor').all()
|
||||
serializer_class = DevolucionSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
estado = self.request.query_params.get('estado_abono')
|
||||
if estado:
|
||||
queryset = queryset.filter(estado_abono=estado)
|
||||
return queryset
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def vincular_abono(self, request, pk=None):
|
||||
"""Vincula una devolución con un albarán de abono"""
|
||||
devolucion = self.get_object()
|
||||
albaran_id = request.data.get('albaran_id')
|
||||
|
||||
if not albaran_id:
|
||||
return Response({'error': 'albaran_id requerido'}, status=400)
|
||||
|
||||
try:
|
||||
albaran = Albaran.objects.get(id=albaran_id)
|
||||
devolucion.albaran_abono = albaran
|
||||
devolucion.estado_abono = 'abonado'
|
||||
devolucion.save()
|
||||
|
||||
return Response(DevolucionSerializer(devolucion).data)
|
||||
except Albaran.DoesNotExist:
|
||||
return Response({'error': 'Albarán no encontrado'}, status=404)
|
||||
|
||||
|
||||
class StockReferenciaViewSet(viewsets.ModelViewSet):
|
||||
queryset = StockReferencia.objects.all()
|
||||
serializer_class = StockReferenciaSerializer
|
||||
|
||||
|
||||
# Vista para Kanban
|
||||
class KanbanView(APIView):
|
||||
"""Vista para obtener datos del Kanban"""
|
||||
|
||||
def get(self, request):
|
||||
pedidos = PedidoCliente.objects.select_related('cliente').prefetch_related(
|
||||
Prefetch('referencias', queryset=ReferenciaPedidoCliente.objects.all())
|
||||
).all()
|
||||
|
||||
# Agrupar por estado
|
||||
kanban_data = {
|
||||
'pendiente_revision': [],
|
||||
'en_revision': [],
|
||||
'pendiente_materiales': [],
|
||||
'completado': [],
|
||||
}
|
||||
|
||||
for pedido in pedidos:
|
||||
pedido_data = PedidoClienteSerializer(pedido).data
|
||||
kanban_data[pedido.estado].append(pedido_data)
|
||||
|
||||
# Si es request HTML, renderizar template
|
||||
if request.accepted_renderer.format == 'html' or 'text/html' in request.META.get('HTTP_ACCEPT', ''):
|
||||
return render(request, 'kanban.html')
|
||||
|
||||
return Response(kanban_data)
|
||||
|
||||
|
||||
# Vista para referencias pendientes por proveedor
|
||||
class ReferenciasProveedorView(APIView):
|
||||
"""Vista para ver referencias pendientes por proveedor"""
|
||||
|
||||
def get(self, request):
|
||||
proveedor_id = request.query_params.get('proveedor_id')
|
||||
|
||||
# Obtener referencias pendientes de pedidos a proveedor
|
||||
queryset = ReferenciaPedidoProveedor.objects.filter(
|
||||
estado__in=['pendiente', 'parcial']
|
||||
).select_related(
|
||||
'pedido_proveedor__proveedor',
|
||||
'referencia_pedido_cliente__pedido_cliente__cliente'
|
||||
)
|
||||
|
||||
if proveedor_id:
|
||||
queryset = queryset.filter(pedido_proveedor__proveedor_id=proveedor_id)
|
||||
|
||||
# Agrupar por proveedor
|
||||
proveedores_data = {}
|
||||
for ref in queryset:
|
||||
proveedor = ref.pedido_proveedor.proveedor
|
||||
if proveedor.id not in proveedores_data:
|
||||
proveedores_data[proveedor.id] = {
|
||||
'proveedor': ProveedorSerializer(proveedor).data,
|
||||
'referencias_pendientes': [],
|
||||
'referencias_devolucion': [],
|
||||
}
|
||||
|
||||
proveedores_data[proveedor.id]['referencias_pendientes'].append(
|
||||
ReferenciaPedidoProveedorSerializer(ref).data
|
||||
)
|
||||
|
||||
# Agregar devoluciones pendientes
|
||||
devoluciones = Devolucion.objects.filter(estado_abono='pendiente')
|
||||
if proveedor_id:
|
||||
devoluciones = devoluciones.filter(proveedor_id=proveedor_id)
|
||||
|
||||
for dev in devoluciones:
|
||||
if dev.proveedor.id not in proveedores_data:
|
||||
proveedores_data[dev.proveedor.id] = {
|
||||
'proveedor': ProveedorSerializer(dev.proveedor).data,
|
||||
'referencias_pendientes': [],
|
||||
'referencias_devolucion': [],
|
||||
}
|
||||
|
||||
proveedores_data[dev.proveedor.id]['referencias_devolucion'].append(
|
||||
DevolucionSerializer(dev).data
|
||||
)
|
||||
|
||||
# Si es request HTML, renderizar template
|
||||
if request.accepted_renderer.format == 'html' or 'text/html' in request.META.get('HTTP_ACCEPT', ''):
|
||||
return render(request, 'proveedores.html', {
|
||||
'proveedores_data': list(proveedores_data.values())
|
||||
})
|
||||
|
||||
return Response(list(proveedores_data.values()))
|
||||
|
||||
|
||||
# Vista para alertas
|
||||
class AlertasView(APIView):
|
||||
"""Vista para obtener alertas (pedidos urgentes)"""
|
||||
|
||||
def get(self, request):
|
||||
ahora = timezone.now()
|
||||
limite = ahora + timedelta(hours=12)
|
||||
|
||||
pedidos_urgentes = PedidoCliente.objects.filter(
|
||||
fecha_cita__gte=ahora,
|
||||
fecha_cita__lte=limite,
|
||||
estado__in=['pendiente_revision', 'en_revision', 'pendiente_materiales']
|
||||
).select_related('cliente').prefetch_related('referencias')
|
||||
|
||||
alertas = []
|
||||
for pedido in pedidos_urgentes:
|
||||
referencias_faltantes = [
|
||||
ref for ref in pedido.referencias.all()
|
||||
if ref.unidades_pendientes > 0
|
||||
]
|
||||
|
||||
if referencias_faltantes:
|
||||
alertas.append({
|
||||
'pedido': PedidoClienteSerializer(pedido).data,
|
||||
'referencias_faltantes': ReferenciaPedidoClienteSerializer(
|
||||
referencias_faltantes, many=True
|
||||
).data,
|
||||
'horas_restantes': (pedido.fecha_cita - ahora).total_seconds() / 3600,
|
||||
})
|
||||
|
||||
return Response(alertas)
|
||||
Reference in New Issue
Block a user