Coverage for backend \ app \ Productos \ repositories \ productoRepository.py: 12.20%
123 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-29 16:13 -0500
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-29 16:13 -0500
1from app.Productos.models.productoModel import Producto
2from app.Productos.schemas.productoSchemas import *
3from app.Productos.models.categoriaProductoModel import CategoriaProducto
4from app.Productos.models.proveedorModel import Proveedor
5from sqlalchemy.orm import joinedload
7class ProductoRepository:
8 def __init__(self, dbSession):
9 self.dbSession = dbSession
11 def listarProductos(self):
12 return self.dbSession.query(Producto).options(joinedload(Producto.categoria), joinedload(Producto.proveedor)).all()
14 def obtenerPorId(self, idProducto: int):
15 return (self.dbSession.query(Producto)
16 .options(joinedload(Producto.categoria), joinedload(Producto.proveedor))
17 .filter(Producto.idProducto == idProducto).first())
19 def validarProductoParaVenta(self, idProducto: int, cantidadSolicitada: float):
20 """Valida que el producto exista, esté activo y tenga stock suficiente.
21 Devuelve un dict con 'error' clave si hay problema, o un dict con keys:
22 - producto: objeto Producto
23 - precio: float (precioUnitarioVenta)
24 - tieneIva: bool
25 - inventario: objeto Inventario (si existe)
26 """
27 producto = self.obtenerPorId(idProducto)
28 if not producto:
29 return {"error": "producto_no_encontrado"}
30 if not getattr(producto, "activoProducto", True):
31 return {"error": "producto_inactivo"}
32 try:
33 from app.Inventario.repositories.inventarioRepository import InventarioRepository
34 inv_repo = InventarioRepository(self.dbSession)
35 inventario = inv_repo.obtenerPorProducto(idProducto)
36 except Exception:
37 inventario = None
38 if not inventario or (inventario.cantidadDisponible or 0) < cantidadSolicitada:
39 return {"error": "stock_insuficiente"}
40 return {
41 "producto": producto,
42 "precio": float(producto.precioUnitarioVenta),
43 "tieneIva": getattr(producto, "tieneIva", True),
44 "inventario": inventario
45 }
47 def crearProducto(self, producto: ProductoCrearSchema):
48 # Verificar fk existencia
49 categoria = self.dbSession.query(CategoriaProducto).filter(CategoriaProducto.idCategoriaProducto == producto.idCategoriaProducto).first()
50 proveedor = self.dbSession.query(Proveedor).filter(Proveedor.idProveedor == producto.idProveedor).first()
51 missing = []
52 if not categoria:
53 missing.append("categoria")
54 else:
55 if not getattr(categoria, "activoCategoria", True):
56 missing.append("categoria_inactiva")
57 if not proveedor:
58 missing.append("proveedor")
59 else:
60 if not getattr(proveedor, "activoProveedor", True):
61 missing.append("proveedor_inactiva")
62 # Validaciones de cantidades no negativas
63 if getattr(producto, "cantidadDisponible", None) is not None and producto.cantidadDisponible < 0:
64 missing.append("cantidad_disponible_negativa")
65 if getattr(producto, "cantidadMinima", None) is not None and producto.cantidadMinima < 0:
66 missing.append("cantidad_minima_negativa")
67 if missing:
68 return {"error": missing}
69 # Crear producto (excluir campos de inventario si vienen)
70 datos_producto = producto.model_dump(exclude={"cantidadDisponible","cantidadMinima"})
71 # Por defecto activoProducto = True al crear (no se acepta en el schema crear)
72 datos_producto.setdefault("activoProducto", True)
73 nuevo = Producto(**datos_producto)
74 self.dbSession.add(nuevo)
75 # Obtener id antes de commit
76 self.dbSession.flush()
77 # Crear inventario automáticamente si no existe
78 try:
79 from app.Inventario.models.inventarioModel import Inventario
80 existe_inv = self.dbSession.query(Inventario).filter(Inventario.idProducto == nuevo.idProducto).first()
81 if not existe_inv:
82 cantidadDisponible = getattr(producto, "cantidadDisponible", None)
83 cantidadMinima = getattr(producto, "cantidadMinima", None)
84 inv_datos = {
85 "idProducto": nuevo.idProducto,
86 "cantidadDisponible": cantidadDisponible if cantidadDisponible is not None else 0,
87 "cantidadMinima": cantidadMinima if cantidadMinima is not None else 0,
88 "activoInventario": True
89 }
90 nuevo_inv = Inventario(**inv_datos)
91 self.dbSession.add(nuevo_inv)
92 inventario_creado = True
93 else:
94 inventario_creado = False
95 except Exception:
96 inventario_creado = False
97 self.dbSession.commit()
98 self.dbSession.refresh(nuevo)
99 # devolver flag para que el servicio pueda ajustar el mensaje
100 return (nuevo, inventario_creado)
102 def modificarProducto(self, idProducto: int, productoActualizar: ProductoActualizarSchema):
103 producto = self.obtenerPorId(idProducto)
104 if not producto:
105 return None
106 datos = productoActualizar.model_dump(exclude_unset=True)
107 camposValidos = ["idCategoriaProducto","idProveedor","nombreProducto","descripcionProducto","precioUnitarioVenta","precioUnitarioCompra","tieneIva","activoProducto"]
108 missing = []
109 for campo, valor in datos.items():
110 if campo in camposValidos:
111 if campo == "idCategoriaProducto":
112 cat = self.dbSession.query(CategoriaProducto).filter(CategoriaProducto.idCategoriaProducto == valor).first()
113 if not cat:
114 missing.append("categoria")
115 continue
116 if campo == "idProveedor":
117 prov = self.dbSession.query(Proveedor).filter(Proveedor.idProveedor == valor).first()
118 if not prov:
119 missing.append("proveedor")
120 continue
121 setattr(producto, campo, valor)
122 if missing:
123 # quitar duplicados y devolver la lista de errores
124 return {"error": list(dict.fromkeys(missing))}
125 self.dbSession.commit()
126 self.dbSession.refresh(producto)
127 return producto
129 def deshabilitarProducto(self, idProducto: int):
130 producto = self.obtenerPorId(idProducto)
131 if not producto:
132 return None
133 producto.activoProducto = False
134 self.dbSession.commit()
135 self.dbSession.refresh(producto)
136 return producto
138 def crearProductosIniciales(self):
139 productos = [
140 {"idCategoriaProducto": 1, "idProveedor": 1, "nombreProducto": "Cola 2 Litros", "descripcionProducto": "Bebida gaseosa sabor cola", "precioUnitarioVenta": 2.50, "precioUnitarioCompra": 1.80, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 100, "cantidadMinima": 10},
141 {"idCategoriaProducto": 1, "idProveedor": 1, "nombreProducto": "Jugo de Naranja 1L", "descripcionProducto": "Jugo natural pasteurizado", "precioUnitarioVenta": 1.80, "precioUnitarioCompra": 1.20, "tieneIva": False, "activoProducto": True, "cantidadDisponible": 80, "cantidadMinima": 10},
142 {"idCategoriaProducto": 1, "idProveedor": 1, "nombreProducto": "Agua Mineral 600ml", "descripcionProducto": "Agua sin gas", "precioUnitarioVenta": 0.70, "precioUnitarioCompra": 0.40, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 200, "cantidadMinima": 20},
143 {"idCategoriaProducto": 1, "idProveedor": 1, "nombreProducto": "Bebida Energética", "descripcionProducto": "Bebida energizante lata", "precioUnitarioVenta": 2.20, "precioUnitarioCompra": 1.50, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 60, "cantidadMinima": 8},
144 {"idCategoriaProducto": 1, "idProveedor": 1, "nombreProducto": "Té Frío Limón", "descripcionProducto": "Té frío sabor limón", "precioUnitarioVenta": 1.50, "precioUnitarioCompra": 1.00, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 90, "cantidadMinima": 10},
146 {"idCategoriaProducto": 2, "idProveedor": 2, "nombreProducto": "Papas Fritas Clásicas", "descripcionProducto": "Papas fritas bolsa mediana", "precioUnitarioVenta": 1.25, "precioUnitarioCompra": 0.80, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 120, "cantidadMinima": 15},
147 {"idCategoriaProducto": 2, "idProveedor": 2, "nombreProducto": "Galletas de Chocolate", "descripcionProducto": "Galletas rellenas", "precioUnitarioVenta": 1.10, "precioUnitarioCompra": 0.75, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 150, "cantidadMinima": 20},
148 {"idCategoriaProducto": 2, "idProveedor": 2, "nombreProducto": "Maní Salado", "descripcionProducto": "Maní tostado y salado", "precioUnitarioVenta": 0.90, "precioUnitarioCompra": 0.50, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 80, "cantidadMinima": 10},
149 {"idCategoriaProducto": 2, "idProveedor": 2, "nombreProducto": "Chifles", "descripcionProducto": "Plátano frito en rodajas", "precioUnitarioVenta": 1.00, "precioUnitarioCompra": 0.65, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 60, "cantidadMinima": 10},
150 {"idCategoriaProducto": 2, "idProveedor": 2, "nombreProducto": "Barra de Cereal", "descripcionProducto": "Barra energética", "precioUnitarioVenta": 0.85, "precioUnitarioCompra": 0.55, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 200, "cantidadMinima": 20},
152 {"idCategoriaProducto": 3, "idProveedor": 3, "nombreProducto": "Arroz 5 Libras", "descripcionProducto": "Arroz blanco grano largo", "precioUnitarioVenta": 3.20, "precioUnitarioCompra": 2.60, "tieneIva": False, "activoProducto": True, "cantidadDisponible": 40, "cantidadMinima": 10},
153 {"idCategoriaProducto": 3, "idProveedor": 3, "nombreProducto": "Azúcar 2 Libras", "descripcionProducto": "Azúcar refinada", "precioUnitarioVenta": 1.90, "precioUnitarioCompra": 1.40, "tieneIva": False, "activoProducto": True, "cantidadDisponible": 60, "cantidadMinima": 10},
154 {"idCategoriaProducto": 3, "idProveedor": 3, "nombreProducto": "Aceite Vegetal 1L", "descripcionProducto": "Aceite comestible", "precioUnitarioVenta": 3.80, "precioUnitarioCompra": 3.10, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 75, "cantidadMinima": 10},
155 {"idCategoriaProducto": 3, "idProveedor": 3, "nombreProducto": "Fideos Spaghetti", "descripcionProducto": "Pasta seca", "precioUnitarioVenta": 1.40, "precioUnitarioCompra": 0.95, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 120, "cantidadMinima": 20},
156 {"idCategoriaProducto": 3, "idProveedor": 3, "nombreProducto": "Atún en Lata", "descripcionProducto": "Atún en aceite", "precioUnitarioVenta": 1.75, "precioUnitarioCompra": 1.20, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 90, "cantidadMinima": 15},
158 {"idCategoriaProducto": 4, "idProveedor": 4, "nombreProducto": "Detergente en Polvo", "descripcionProducto": "Detergente multiuso", "precioUnitarioVenta": 4.50, "precioUnitarioCompra": 3.60, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 50, "cantidadMinima": 10},
159 {"idCategoriaProducto": 4, "idProveedor": 4, "nombreProducto": "Cloro 1L", "descripcionProducto": "Desinfectante", "precioUnitarioVenta": 1.20, "precioUnitarioCompra": 0.80, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 100, "cantidadMinima": 20},
160 {"idCategoriaProducto": 4, "idProveedor": 4, "nombreProducto": "Esponjas", "descripcionProducto": "Esponjas para lavar", "precioUnitarioVenta": 1.00, "precioUnitarioCompra": 0.60, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 250, "cantidadMinima": 30},
161 {"idCategoriaProducto": 4, "idProveedor": 4, "nombreProducto": "Limpiavidrios", "descripcionProducto": "Limpieza de superficies", "precioUnitarioVenta": 2.30, "precioUnitarioCompra": 1.70, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 60, "cantidadMinima": 10},
162 {"idCategoriaProducto": 4, "idProveedor": 4, "nombreProducto": "Desinfectante Piso", "descripcionProducto": "Aroma floral", "precioUnitarioVenta": 2.80, "precioUnitarioCompra": 2.10, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 40, "cantidadMinima": 8},
164 {"idCategoriaProducto": 5, "idProveedor": 5, "nombreProducto": "Jabón de Baño", "descripcionProducto": "Jabón antibacterial", "precioUnitarioVenta": 1.10, "precioUnitarioCompra": 0.70, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 180, "cantidadMinima": 30},
165 {"idCategoriaProducto": 5, "idProveedor": 5, "nombreProducto": "Shampoo", "descripcionProducto": "Shampoo familiar", "precioUnitarioVenta": 4.20, "precioUnitarioCompra": 3.30, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 45, "cantidadMinima": 8},
166 {"idCategoriaProducto": 5, "idProveedor": 5, "nombreProducto": "Pasta Dental", "descripcionProducto": "Protección dental", "precioUnitarioVenta": 2.50, "precioUnitarioCompra": 1.90, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 110, "cantidadMinima": 20},
167 {"idCategoriaProducto": 5, "idProveedor": 5, "nombreProducto": "Papel Higiénico", "descripcionProducto": "Paquete 4 rollos", "precioUnitarioVenta": 3.60, "precioUnitarioCompra": 2.80, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 70, "cantidadMinima": 10},
168 {"idCategoriaProducto": 5, "idProveedor": 5, "nombreProducto": "Desodorante", "descripcionProducto": "Protección 24 horas", "precioUnitarioVenta": 3.90, "precioUnitarioCompra": 3.00, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 65, "cantidadMinima": 10},
169 ]
171 for p in productos:
172 existe = self.dbSession.query(Producto).filter(Producto.nombreProducto == p["nombreProducto"]).first()
173 if not existe:
174 # Verificar fk antes de crear
175 cat = self.dbSession.query(CategoriaProducto).filter(CategoriaProducto.idCategoriaProducto == p["idCategoriaProducto"]).first()
176 prov = self.dbSession.query(Proveedor).filter(Proveedor.idProveedor == p["idProveedor"]).first()
177 if cat and prov:
178 # Excluir campos de inventario al crear Producto
179 datos_producto = {k: v for k, v in p.items() if k not in ("cantidadDisponible", "cantidadMinima")}
180 datos_producto.setdefault("activoProducto", True)
181 nuevo = Producto(**datos_producto)
182 self.dbSession.add(nuevo)
183 # flush para obtener el idProducto y crear inventario inicial si no existe
184 self.dbSession.flush()
185 try:
186 from app.Inventario.models.inventarioModel import Inventario
187 existe_inv = self.dbSession.query(Inventario).filter(Inventario.idProducto == nuevo.idProducto).first()
188 if not existe_inv:
189 inv = Inventario(idProducto=nuevo.idProducto, cantidadDisponible=p.get("cantidadDisponible", 0), cantidadMinima=p.get("cantidadMinima", 0), activoInventario=True)
190 self.dbSession.add(inv)
191 except Exception:
192 # Si Inventario no existe o hay problema, skip
193 pass
194 print("Productos por defecto creados...!")
195 self.dbSession.commit()
196 self.dbSession.close()