Coverage for backend \ app \ Caja \ repositories \ cajaRepository.py: 11.90%
126 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 datetime import datetime, timezone, timedelta, date
3from app.Inventario.repositories.inventarioRepository import InventarioRepository
4from app.Productos.models.productoModel import Producto
5from app.Caja.models.cajaHistorialModel import CajaHistorial
6class CajaRepository:
7 def __init__(self, dbSession):
8 self.dbSession = dbSession
10 def crearCajaHistorial(self, montoInicialDeclarado: float, idUsuarioCaja: int):
11 # Verificar que no haya una caja ABIERTA para ese usuario
12 abierta = self.dbSession.query(CajaHistorial).filter(CajaHistorial.idUsuarioCaja == idUsuarioCaja, CajaHistorial.estadoCaja == "ABIERTA").first()
13 if abierta:
14 return {"error": "caja_abierta_existente"}
15 # Verificar que no exista ya una caja del mismo dia para el usuario
16 quitoTZ = timezone(timedelta(hours=-5))
17 hoy = datetime.now(quitoTZ).date()
18 existe_hoy = self.dbSession.query(CajaHistorial).filter(CajaHistorial.idUsuarioCaja == idUsuarioCaja).filter(CajaHistorial.fechaAperturaCaja >= datetime.combine(hoy, datetime.min.time()).astimezone(quitoTZ)).first()
19 if existe_hoy:
20 return {"error": "caja_ya_abierta_hoy"}
21 nuevo = CajaHistorial(
22 idUsuarioCaja=idUsuarioCaja,
23 fechaAperturaCaja=datetime.now(quitoTZ),
24 montoInicialDeclarado=montoInicialDeclarado,
25 estadoCaja="ABIERTA",
26 detalle=f"Apertura: montoInicialDeclarado: {montoInicialDeclarado}"
27 )
28 # Nota: el detalle específico (actor) se puede completar por el servicio si se dispone del usuario que realiza la acción.
29 self.dbSession.add(nuevo)
30 self.dbSession.commit()
31 self.dbSession.refresh(nuevo)
32 return nuevo
34 def cerrarCaja(self, idCaja: int, montoFinalDeclarado: float, closedBy: str | None = None, admin: bool = False):
35 caja = self.obtenerPorId(idCaja)
36 if not caja:
37 return None
38 if caja.estadoCaja != "ABIERTA":
39 if caja.estadoCaja == "CERRADA":
40 return {"error": "caja_ya_cerrada", "caja": caja}
41 return {"error": "caja_no_abierta"}
42 tz = timezone(timedelta(hours=-5))
43 hoy = datetime.now(tz).date()
44 fecha_cierre_dt = datetime.now(tz)
45 if not admin:
46 # Usuario normal: solo cerrar si la apertura fue hoy
47 if caja.fechaAperturaCaja.date() != hoy:
48 return {"error": "cierre_fuera_de_dia"}
49 detalle_base = f"Cierre: cerrado por {closedBy} en la jornada correcta" if closedBy else "Cierre"
50 else:
51 # Admin: puede cerrar cualquier caja; si difiere de la jornada, marcar anomalía
52 if caja.fechaAperturaCaja.date() != hoy:
53 detalle_base = f"Cierre con anomalías (fecha cierre: {hoy.isoformat()})"
54 else:
55 detalle_base = "Cierre"
56 if closedBy:
57 detalle_base += f" por {closedBy}"
58 caja.fechaCierreCaja = fecha_cierre_dt
59 caja.montoCierreDeclarado = montoFinalDeclarado
60 # Calcular montoCierreSistema: montoInicialDeclarado + suma de ventas en efectivo y no anuladas asociadas a esta caja
61 try:
62 from app.Venta.repositories.ventaRepository import VentaRepository
63 venta_repo = VentaRepository(self.dbSession)
64 ventas_efectivo = venta_repo.sumarVentasEfectivoNoAnuladasPorCaja(caja.idCaja)
65 except Exception:
66 ventas_efectivo = 0.0
67 monto_inicial = caja.montoInicialDeclarado or 0.0
68 monto_sistema = round(monto_inicial + (ventas_efectivo or 0.0), 2)
69 caja.montoCierreSistema = monto_sistema
70 caja.diferenciaCaja = round(montoFinalDeclarado - monto_sistema, 2)
71 caja.estadoCaja = "CERRADA"
72 caja.detalle = f"{detalle_base}; montoFinalDeclarado: {montoFinalDeclarado}; montoCierreSistema: {monto_sistema}; montoInicialDeclarado: {monto_inicial}"
73 self.dbSession.commit()
74 self.dbSession.refresh(caja)
75 return caja
77 def cerrarCajaPendiente(self, idCaja: int, montoFinalDeclarado: float, comentario: str | None = None, montoCierreSistema: float | None = None):
78 caja = self.obtenerPorId(idCaja)
79 if not caja:
80 return None
81 if caja.estadoCaja != "ABIERTA":
82 if caja.estadoCaja == "CERRADA":
83 return {"error": "caja_ya_cerrada", "caja": caja}
84 return {"error": "caja_no_abierta"}
85 # Permitir cerrar aunque sea otro dia
86 tz = timezone(timedelta(hours=-5))
87 caja.fechaCierreCaja = datetime.now(tz)
88 caja.montoCierreDeclarado = montoFinalDeclarado
89 # Si no se pasó montoCierreSistema, calcularlo (montoInicialDeclarado + ventas en efectivo no anuladas)
90 if montoCierreSistema is None:
91 try:
92 from app.Venta.repositories.ventaRepository import VentaRepository
93 venta_repo = VentaRepository(self.dbSession)
94 ventas_efectivo = venta_repo.sumarVentasEfectivoNoAnuladasPorCaja(caja.idCaja)
95 except Exception:
96 ventas_efectivo = 0.0
97 monto_inicial = caja.montoInicialDeclarado or 0.0
98 montoCierreSistema = round(monto_inicial + (ventas_efectivo or 0.0), 2)
99 caja.montoCierreSistema = montoCierreSistema
100 caja.diferenciaCaja = round(montoFinalDeclarado - (montoCierreSistema or 0), 2)
101 caja.estadoCaja = "CERRADA"
102 detalle = f"Cierre fuera de tiempo"
103 if comentario:
104 detalle += f": {comentario}"
105 detalle += f"; montoFinalDeclarado: {montoFinalDeclarado}; montoCierreSistema: {montoCierreSistema}"
106 caja.detalle = detalle
107 self.dbSession.commit()
108 self.dbSession.refresh(caja)
109 return caja
111 def obtenerPorId(self, idCaja: int):
112 return self.dbSession.query(CajaHistorial).filter(CajaHistorial.idCaja == idCaja).first()
114 def reabrirCaja(self, idCaja: int, reabiertaPor: str | None = None):
115 caja = self.obtenerPorId(idCaja)
116 if not caja:
117 return None
118 if caja.estadoCaja != "CERRADA":
119 return {"error": "caja_no_cerrada", "caja": caja}
120 # Reabrir: limpiar campos de cierre y dejar ABIERTA
121 caja.fechaCierreCaja = None
122 caja.montoCierreDeclarado = None
123 caja.montoCierreSistema = None
124 caja.diferenciaCaja = None
125 caja.estadoCaja = "ABIERTA"
126 detalle_prev = caja.detalle or ""
127 detalle_nuevo = detalle_prev + ("; "+f"Reabierta por {reabiertaPor}" if reabiertaPor else "; Reabierta")
128 caja.detalle = detalle_nuevo
129 self.dbSession.commit()
130 self.dbSession.refresh(caja)
131 return caja
134 def listarCajasHoy(self, idUsuario: int = None, esAdmin: bool = False):
135 tz = timezone(timedelta(hours=-5))
136 hoy = datetime.now(tz).date()
137 inicio = datetime.combine(hoy, datetime.min.time()).astimezone(tz)
138 fin = datetime.combine(hoy, datetime.max.time()).replace(hour=23, minute=59, second=59, microsecond=0).astimezone(tz)
139 query = self.dbSession.query(CajaHistorial).filter(CajaHistorial.fechaAperturaCaja >= inicio, CajaHistorial.fechaAperturaCaja <= fin)
140 if not esAdmin and idUsuario:
141 query = query.filter(CajaHistorial.idUsuarioCaja == idUsuario)
142 return query.all()
144 def listarCajas(self, idUsuario: int = None, esAdmin: bool = False):
145 if esAdmin:
146 return self.dbSession.query(CajaHistorial).all()
147 return self.dbSession.query(CajaHistorial).filter(CajaHistorial.idUsuarioCaja == idUsuario).all()
149 def filtrarCaja(self, idUsuario: int, fecha: date):
150 tz = timezone(timedelta(hours=-5))
151 inicio = datetime.combine(fecha, datetime.min.time()).astimezone(tz)
152 fin = datetime.combine(fecha, datetime.max.time()).replace(hour=23, minute=59, second=59, microsecond=0).astimezone(tz)
153 return self.dbSession.query(CajaHistorial).filter(CajaHistorial.idUsuarioCaja == idUsuario, CajaHistorial.fechaAperturaCaja >= inicio, CajaHistorial.fechaAperturaCaja <= fin).all()