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

1from datetime import datetime, timezone, timedelta, date 

2 

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 

9 

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 

33 

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 

76 

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 

110 

111 def obtenerPorId(self, idCaja: int): 

112 return self.dbSession.query(CajaHistorial).filter(CajaHistorial.idCaja == idCaja).first() 

113 

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 

132 

133 

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() 

143 

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() 

148 

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()