Files
pedidosClientes/gestion_pedidos/views.py
2025-12-05 11:27:16 -03:00

368 lines
14 KiB
Python

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)