355 lines
12 KiB
Python
355 lines
12 KiB
Python
from django.db import models
|
|
from django.core.validators import MinValueValidator
|
|
from django.utils import timezone
|
|
|
|
|
|
class Cliente(models.Model):
|
|
"""Cliente con información del vehículo"""
|
|
nombre = models.CharField(max_length=200)
|
|
matricula_vehiculo = models.CharField(max_length=20, unique=True)
|
|
telefono = models.CharField(max_length=20, blank=True, null=True)
|
|
email = models.EmailField(blank=True, null=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
db_table = 'clientes'
|
|
indexes = [
|
|
models.Index(fields=['matricula_vehiculo']),
|
|
models.Index(fields=['nombre']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"{self.nombre} - {self.matricula_vehiculo}"
|
|
|
|
|
|
class PedidoCliente(models.Model):
|
|
"""Pedido de recambios de un cliente"""
|
|
ESTADOS = [
|
|
('pendiente_revision', 'Pendiente Revisión'),
|
|
('en_revision', 'En Revisión'),
|
|
('pendiente_materiales', 'Pendiente Materiales'),
|
|
('completado', 'Completado'),
|
|
]
|
|
|
|
numero_pedido = models.CharField(max_length=50, unique=True, db_index=True)
|
|
cliente = models.ForeignKey(Cliente, on_delete=models.CASCADE, related_name='pedidos')
|
|
fecha_pedido = models.DateTimeField(default=timezone.now)
|
|
fecha_cita = models.DateTimeField(blank=True, null=True)
|
|
estado = models.CharField(max_length=30, choices=ESTADOS, default='pendiente_revision')
|
|
presupuesto_id = models.CharField(max_length=50, blank=True, null=True)
|
|
archivo_pdf_path = models.CharField(max_length=500, blank=True, null=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
db_table = 'pedidos_cliente'
|
|
indexes = [
|
|
models.Index(fields=['numero_pedido']),
|
|
models.Index(fields=['estado']),
|
|
models.Index(fields=['fecha_cita']),
|
|
models.Index(fields=['fecha_pedido']),
|
|
]
|
|
ordering = ['-fecha_pedido']
|
|
|
|
def __str__(self):
|
|
return f"Pedido {self.numero_pedido} - {self.cliente.matricula_vehiculo}"
|
|
|
|
@property
|
|
def es_urgente(self):
|
|
"""Verifica si el pedido es urgente (menos de 12 horas para la cita)"""
|
|
if not self.fecha_cita:
|
|
return False
|
|
tiempo_restante = self.fecha_cita - timezone.now()
|
|
return tiempo_restante.total_seconds() < 12 * 3600 and tiempo_restante.total_seconds() > 0
|
|
|
|
|
|
class ReferenciaPedidoCliente(models.Model):
|
|
"""Referencias (piezas) de un pedido de cliente"""
|
|
ESTADOS = [
|
|
('pendiente', 'Pendiente'),
|
|
('parcial', 'Parcial'),
|
|
('completo', 'Completo'),
|
|
]
|
|
|
|
pedido_cliente = models.ForeignKey(
|
|
PedidoCliente,
|
|
on_delete=models.CASCADE,
|
|
related_name='referencias'
|
|
)
|
|
referencia = models.CharField(max_length=100, db_index=True)
|
|
denominacion = models.CharField(max_length=500)
|
|
unidades_solicitadas = models.IntegerField(validators=[MinValueValidator(1)])
|
|
unidades_en_stock = models.IntegerField(default=0, validators=[MinValueValidator(0)])
|
|
unidades_pendientes = models.IntegerField(default=0, validators=[MinValueValidator(0)])
|
|
estado = models.CharField(max_length=20, choices=ESTADOS, default='pendiente')
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
db_table = 'referencias_pedido_cliente'
|
|
indexes = [
|
|
models.Index(fields=['referencia']),
|
|
models.Index(fields=['estado']),
|
|
models.Index(fields=['pedido_cliente', 'estado']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"{self.referencia} - {self.denominacion} ({self.unidades_solicitadas} unidades)"
|
|
|
|
def calcular_estado(self):
|
|
"""Calcula el estado basado en unidades"""
|
|
if self.unidades_pendientes <= 0:
|
|
return 'completo'
|
|
elif self.unidades_pendientes < self.unidades_solicitadas:
|
|
return 'parcial'
|
|
return 'pendiente'
|
|
|
|
def save(self, *args, **kwargs):
|
|
self.unidades_pendientes = max(0, self.unidades_solicitadas - self.unidades_en_stock)
|
|
self.estado = self.calcular_estado()
|
|
super().save(*args, **kwargs)
|
|
|
|
|
|
class Proveedor(models.Model):
|
|
"""Proveedor de recambios"""
|
|
nombre = models.CharField(max_length=200, db_index=True)
|
|
email = models.EmailField(blank=True, null=True)
|
|
tiene_web = models.BooleanField(default=True)
|
|
activo = models.BooleanField(default=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
db_table = 'proveedores'
|
|
indexes = [
|
|
models.Index(fields=['nombre']),
|
|
models.Index(fields=['activo']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return self.nombre
|
|
|
|
|
|
class PedidoProveedor(models.Model):
|
|
"""Pedido realizado a un proveedor"""
|
|
ESTADOS = [
|
|
('pendiente_recepcion', 'Pendiente Recepción'),
|
|
('parcial', 'Parcial'),
|
|
('completado', 'Completado'),
|
|
('cancelado', 'Cancelado'),
|
|
]
|
|
|
|
TIPOS = [
|
|
('web', 'Web'),
|
|
('manual', 'Manual'),
|
|
]
|
|
|
|
proveedor = models.ForeignKey(
|
|
Proveedor,
|
|
on_delete=models.CASCADE,
|
|
related_name='pedidos'
|
|
)
|
|
numero_pedido = models.CharField(max_length=100, blank=True, null=True)
|
|
fecha_pedido = models.DateTimeField(default=timezone.now)
|
|
email_confirmacion_path = models.CharField(max_length=500, blank=True, null=True)
|
|
estado = models.CharField(max_length=30, choices=ESTADOS, default='pendiente_recepcion')
|
|
tipo = models.CharField(max_length=20, choices=TIPOS, default='web')
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
db_table = 'pedidos_proveedor'
|
|
indexes = [
|
|
models.Index(fields=['proveedor', 'estado']),
|
|
models.Index(fields=['fecha_pedido']),
|
|
models.Index(fields=['estado']),
|
|
]
|
|
ordering = ['-fecha_pedido']
|
|
|
|
def __str__(self):
|
|
return f"Pedido {self.numero_pedido or self.id} - {self.proveedor.nombre}"
|
|
|
|
|
|
class ReferenciaPedidoProveedor(models.Model):
|
|
"""Referencias pedidas a un proveedor"""
|
|
ESTADOS = [
|
|
('pendiente', 'Pendiente'),
|
|
('parcial', 'Parcial'),
|
|
('recibido', 'Recibido'),
|
|
]
|
|
|
|
pedido_proveedor = models.ForeignKey(
|
|
PedidoProveedor,
|
|
on_delete=models.CASCADE,
|
|
related_name='referencias'
|
|
)
|
|
referencia_pedido_cliente = models.ForeignKey(
|
|
ReferenciaPedidoCliente,
|
|
on_delete=models.CASCADE,
|
|
related_name='pedidos_proveedor',
|
|
blank=True,
|
|
null=True
|
|
)
|
|
referencia = models.CharField(max_length=100, db_index=True)
|
|
denominacion = models.CharField(max_length=500)
|
|
unidades_pedidas = models.IntegerField(validators=[MinValueValidator(1)])
|
|
unidades_recibidas = models.IntegerField(default=0, validators=[MinValueValidator(0)])
|
|
estado = models.CharField(max_length=20, choices=ESTADOS, default='pendiente')
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
db_table = 'referencias_pedido_proveedor'
|
|
indexes = [
|
|
models.Index(fields=['referencia']),
|
|
models.Index(fields=['estado']),
|
|
models.Index(fields=['pedido_proveedor', 'estado']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"{self.referencia} - {self.denominacion} ({self.unidades_pedidas} unidades)"
|
|
|
|
|
|
class Albaran(models.Model):
|
|
"""Albarán recibido de un proveedor"""
|
|
ESTADOS_PROCESADO = [
|
|
('pendiente', 'Pendiente'),
|
|
('procesado', 'Procesado'),
|
|
('clasificacion', 'Pendiente Clasificación'),
|
|
('error', 'Error'),
|
|
]
|
|
|
|
proveedor = models.ForeignKey(
|
|
Proveedor,
|
|
on_delete=models.CASCADE,
|
|
related_name='albaranes',
|
|
blank=True,
|
|
null=True
|
|
)
|
|
numero_albaran = models.CharField(max_length=100, blank=True, null=True)
|
|
fecha_albaran = models.DateField(blank=True, null=True)
|
|
archivo_path = models.CharField(max_length=500)
|
|
estado_procesado = models.CharField(
|
|
max_length=30,
|
|
choices=ESTADOS_PROCESADO,
|
|
default='pendiente'
|
|
)
|
|
fecha_procesado = models.DateTimeField(blank=True, null=True)
|
|
datos_ocr = models.JSONField(default=dict, blank=True) # Datos extraídos por OCR
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
db_table = 'albaranes'
|
|
indexes = [
|
|
models.Index(fields=['proveedor', 'estado_procesado']),
|
|
models.Index(fields=['numero_albaran']),
|
|
models.Index(fields=['fecha_albaran']),
|
|
models.Index(fields=['estado_procesado']),
|
|
]
|
|
ordering = ['-created_at']
|
|
|
|
def __str__(self):
|
|
return f"Albarán {self.numero_albaran or self.id} - {self.proveedor.nombre if self.proveedor else 'Sin proveedor'}"
|
|
|
|
|
|
class ReferenciaAlbaran(models.Model):
|
|
"""Referencias contenidas en un albarán"""
|
|
TIPOS_IMPUESTO = [
|
|
('21', '21%'),
|
|
('10', '10%'),
|
|
('7', '7%'),
|
|
('4', '4%'),
|
|
('3', '3%'),
|
|
('0', '0%'),
|
|
]
|
|
|
|
albaran = models.ForeignKey(
|
|
Albaran,
|
|
on_delete=models.CASCADE,
|
|
related_name='referencias'
|
|
)
|
|
referencia = models.CharField(max_length=100, db_index=True)
|
|
denominacion = models.CharField(max_length=500)
|
|
unidades = models.IntegerField(validators=[MinValueValidator(1)])
|
|
precio_unitario = models.DecimalField(max_digits=10, decimal_places=2, default=0)
|
|
impuesto_tipo = models.CharField(max_length=5, choices=TIPOS_IMPUESTO, default='21')
|
|
impuesto_valor = models.DecimalField(max_digits=10, decimal_places=2, default=0)
|
|
referencia_pedido_proveedor = models.ForeignKey(
|
|
ReferenciaPedidoProveedor,
|
|
on_delete=models.SET_NULL,
|
|
related_name='referencias_albaran',
|
|
blank=True,
|
|
null=True
|
|
)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
db_table = 'referencias_albaran'
|
|
indexes = [
|
|
models.Index(fields=['referencia']),
|
|
models.Index(fields=['albaran']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"{self.referencia} - {self.denominacion} ({self.unidades} unidades)"
|
|
|
|
|
|
class Devolucion(models.Model):
|
|
"""Devolución de material a proveedor"""
|
|
ESTADOS_ABONO = [
|
|
('pendiente', 'Pendiente Abono'),
|
|
('abonado', 'Abonado'),
|
|
]
|
|
|
|
proveedor = models.ForeignKey(
|
|
Proveedor,
|
|
on_delete=models.CASCADE,
|
|
related_name='devoluciones'
|
|
)
|
|
referencia = models.CharField(max_length=100, db_index=True)
|
|
denominacion = models.CharField(max_length=500, blank=True, null=True)
|
|
unidades = models.IntegerField(validators=[MinValueValidator(1)])
|
|
fecha_devolucion = models.DateTimeField(default=timezone.now)
|
|
estado_abono = models.CharField(max_length=20, choices=ESTADOS_ABONO, default='pendiente')
|
|
albaran_abono = models.ForeignKey(
|
|
Albaran,
|
|
on_delete=models.SET_NULL,
|
|
related_name='devoluciones',
|
|
blank=True,
|
|
null=True
|
|
)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
db_table = 'devoluciones'
|
|
indexes = [
|
|
models.Index(fields=['proveedor', 'estado_abono']),
|
|
models.Index(fields=['referencia']),
|
|
models.Index(fields=['estado_abono']),
|
|
]
|
|
ordering = ['-fecha_devolucion']
|
|
|
|
def __str__(self):
|
|
return f"Devolución {self.referencia} - {self.proveedor.nombre} ({self.unidades} unidades)"
|
|
|
|
|
|
class StockReferencia(models.Model):
|
|
"""Stock disponible de una referencia"""
|
|
referencia = models.CharField(max_length=100, unique=True, db_index=True)
|
|
unidades_disponibles = models.IntegerField(default=0, validators=[MinValueValidator(0)])
|
|
ultima_actualizacion = models.DateTimeField(auto_now=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
db_table = 'stock_referencias'
|
|
indexes = [
|
|
models.Index(fields=['referencia']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"{self.referencia} - {self.unidades_disponibles} unidades"
|
|
|