importar_movimientos_externos.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import logging
  2. from django.utils.timezone import make_aware
  3. from decimal import Decimal
  4. from django.core.management.base import BaseCommand
  5. from datetime import datetime
  6. from django.utils.dateparse import parse_datetime
  7. import json
  8. import os
  9. from finanzas.models import Cuenta, Movimiento, Categoria
  10. from reymotausers.models import ReyMotaUser
  11. logger = logging.getLogger('importar_movimientos_externos')
  12. handler = logging.FileHandler('importar_movimientos.log')
  13. formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
  14. handler.setFormatter(formatter)
  15. logger.addHandler(handler)
  16. logger.setLevel(logging.INFO)
  17. class Command(BaseCommand):
  18. help = 'Importa movimientos externos desde un archivo JSON'
  19. def add_arguments(self, parser):
  20. parser.add_argument('archivo_json', type=str)
  21. def handle(self, *args, **options):
  22. ruta = options['archivo_json']
  23. if not os.path.exists(ruta):
  24. self.stdout.write(self.style.ERROR(f'Archivo no encontrado: {ruta}'))
  25. return
  26. with open(ruta, 'r', encoding='utf-8') as f:
  27. datos = json.load(f)
  28. for registro in datos:
  29. try:
  30. user = ReyMotaUser.objects.get(id=registro['user_id'])
  31. except ReyMotaUser.DoesNotExist:
  32. logger.warning(f"Usuario con id {registro['user_id']} no encontrado. Movimiento omitido.")
  33. continue
  34. nombre_origen = registro['source_name']
  35. nombre_destino = registro['destination_name']
  36. monto_raw = str(registro['amount']).replace(',', '.')
  37. try:
  38. monto = abs(Decimal(monto_raw))
  39. except:
  40. logger.warning(f"Monto inválido: {registro['amount']}")
  41. continue
  42. if nombre_origen == nombre_destino:
  43. logger.info(f"Movimiento con cuentas iguales omitido: {nombre_origen}")
  44. continue
  45. cuenta_origen, _ = Cuenta.objects.get_or_create(nombre=nombre_origen, usuario=user, defaults={'tipo': 'gasto'})
  46. cuenta_destino, _ = Cuenta.objects.get_or_create(nombre=nombre_destino, usuario=user, defaults={'tipo': 'ingreso'})
  47. fecha = make_aware(datetime.fromisoformat(registro['date'][:19]))
  48. descripcion = registro.get('description', '')
  49. # Detección de duplicado
  50. duplicado = Movimiento.objects.filter(
  51. usuario=user,
  52. cuenta_origen=cuenta_origen,
  53. cuenta_destino=cuenta_destino,
  54. monto=monto,
  55. fecha=fecha,
  56. descripcion=descripcion
  57. ).exists()
  58. if duplicado:
  59. logger.info(f"Duplicado omitido: {descripcion} - {monto} ({fecha})")
  60. continue
  61. mov = Movimiento.objects.create(
  62. usuario=user,
  63. cuenta_origen=cuenta_origen,
  64. cuenta_destino=cuenta_destino,
  65. monto=monto,
  66. fecha=fecha,
  67. descripcion=descripcion
  68. )
  69. if cat := registro.get('category'):
  70. categoria, _ = Categoria.objects.get_or_create(nombre=cat)
  71. mov.categoria = categoria
  72. mov.save()
  73. try:
  74. cuenta_origen.saldo -= monto
  75. cuenta_origen.save()
  76. except:
  77. logger.warning(f"Monto inválido: {registro['amount']} {registro['journal_id']}")
  78. continue
  79. try:
  80. cuenta_destino.saldo += monto
  81. cuenta_destino.save()
  82. except:
  83. logger.warning(f"Monto inválido: {registro['amount']}")
  84. continue
  85. logger.info(f"Movimiento importado: {descripcion} - {monto} ({fecha})")
  86. self.stdout.write(self.style.SUCCESS('Importación finalizada. Ver "importar_movimientos.log" para detalles.'))