import tempfile from api.utils.storage_service import storage_service from celery import shared_task from api.organization.models import Organizacion from django.core.files.base import ContentFile from django.utils import timezone from api.reports.models import ReportDocument from api.customs.models import Pedimento, Cove, EDocument, Partida from django.db.models import Q, Exists, OuterRef # from django.db.models import Q, from api.record.models import Document import csv import os from django.conf import settings from django.core.files.uploadedfile import SimpleUploadedFile @shared_task def generate_report_document(report_id): try: report = ReportDocument.objects.get(id=report_id) report.status = 'processing' report.save(update_fields=['status']) filters = report.filters or {} pedimentos_filters = Q() if filters.get('organizacion_id'): pedimentos_filters &= Q(organizacion_id=filters['organizacion_id']) if filters.get('fecha_pago__gte'): pedimentos_filters &= Q(fecha_pago__gte=filters['fecha_pago__gte']) if filters.get('fecha_pago__lte'): pedimentos_filters &= Q(fecha_pago__lte=filters['fecha_pago__lte']) if filters.get('contribuyente__rfc'): pedimentos_filters &= Q(contribuyente__rfc=filters['contribuyente__rfc']) if filters.get('patente'): pedimentos_filters &= Q(patente=filters['patente']) if filters.get('aduana'): pedimentos_filters &= Q(aduana=filters['aduana']) if filters.get('pedimento'): pedimentos_filters &= Q(pedimento=filters['pedimento']) if filters.get('pedimento_app'): pedimentos_filters &= Q(pedimento_app=filters['pedimento_app']) if filters.get('regimen'): pedimentos_filters &= Q(regimen=filters['regimen']) if filters.get('tipo_operacion'): pedimentos_filters &= Q(tipo_operacion_id=filters['tipo_operacion']) # Consulta asíncrona de los modelos pedimentos = Pedimento.objects.filter(pedimentos_filters) filename = filters.get('filename') if filename: filename = f"{filename}.csv" if not filename.endswith('.csv') else filename else: filename = f"report_{report.id}_{timezone.now().strftime('%Y%m%d%H%M%S')}.csv" with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.csv', encoding='utf-8', newline='') as f: tmp_path = f.name # Escribir CSV en archivo temporal with open(tmp_path, 'w', newline='', encoding='utf-8') as f: writer = csv.writer(f) headers = [ 'aduana', 'patente', 'regimen', 'pedimento', 'pedimento_app', 'clave_pedimento', 'tipo_operacion_id', 'contribuyente_id', 'tipo_documento', 'numero_documento', 'estado', 'acuse_estado' ] writer.writerow(headers) for ped in pedimentos: for cove in Cove.objects.filter(pedimento=ped): writer.writerow([ ped.aduana, ped.patente, ped.regimen, ped.pedimento, ped.pedimento_app, ped.clave_pedimento, ped.tipo_operacion_id, ped.contribuyente_id, 'COVE', cove.numero_cove, cove.cove_descargado, cove.acuse_cove_descargado ]) for edoc in EDocument.objects.filter(pedimento=ped): writer.writerow([ ped.aduana, ped.patente, ped.regimen, ped.pedimento, ped.pedimento_app, ped.clave_pedimento, ped.tipo_operacion_id, ped.contribuyente_id, 'EDOC', edoc.numero_edocument, edoc.edocument_descargado, edoc.acuse_descargado ]) for partida in Partida.objects.filter(pedimento=ped): writer.writerow([ ped.aduana, ped.patente, ped.regimen, ped.pedimento, ped.pedimento_app, ped.clave_pedimento, ped.tipo_operacion_id, ped.contribuyente_id, 'PARTIDA', partida.numero_partida, partida.descargado, '' ]) # ============ NUEVO: Guardar en MinIO ============ # Leer archivo temporal with open(tmp_path, 'rb') as f: file_content = f.read() # Crear UploadedFile uploaded_file = SimpleUploadedFile( name=filename, content=file_content, content_type='text/csv' ) # Guardar en storage ruta = storage_service.save_report( file=uploaded_file, organizacion_id=filters.get('organizacion_id'), metadata={ 'report_id': str(report.id), 'report_type': 'cumplimiento', 'user_id': str(report.user.id) if report.user else None } ) if ruta: report.file = ruta report.status = 'ready' else: report.status = 'error' report.error_message = 'Error al guardar el archivo en storage' # Limpiar temporal os.unlink(tmp_path) report.finished_at = timezone.now() report.save(update_fields=['status', 'file', 'finished_at', 'error_message']) except Exception as e: report.status = 'error' report.error_message = str(e) report.finished_at = timezone.now() report.save(update_fields=['status', 'error_message', 'finished_at']) @shared_task def generate_report_control_pedimento(report_id): try: report = ReportDocument.objects.get(id=report_id) report.status = 'processing' report.save(update_fields=['status']) filters = report.filters or {} # Construir filtros pedimentos_filters = {} if filters.get('organizacion_id'): pedimentos_filters['organizacion_id'] = filters['organizacion_id'] if filters.get('fecha_pago__gte'): pedimentos_filters['fecha_pago__gte'] = filters['fecha_pago__gte'] if filters.get('fecha_pago__lte'): pedimentos_filters['fecha_pago__lte'] = filters['fecha_pago__lte'] if filters.get('pedimento_app'): pedimentos_filters['pedimento_app'] = filters['pedimento_app'] # pedimentos por organizacion pedimentos_qs = Pedimento.objects.filter(**pedimentos_filters) pedimentos_total = pedimentos_qs.count() pedimento_ids = list(pedimentos_qs.values_list('id', flat=True)) rfcs_raw = list(pedimentos_qs.values_list('agente_aduanal', flat=True)) # inicializar totales pedimentos_completos = 0 total_documentos = 0 documentos_sin_descargar = 0 nombre_organizacion = '' if filters.get('organizacion_id'): try: # Asumo que tienes un modelo Organizacion - ajusta según tu modelo real organizacion = Organizacion.objects.get(id=filters['organizacion_id']) nombre_organizacion = organizacion.nombre # ajusta el campo según tu modelo except Organizacion.DoesNotExist: nombre_organizacion = f"ID: {filters['organizacion_id']}" except Exception as e: nombre_organizacion = f"Error: {str(e)}" # lista de rfc rfc_list = ', '.join(sorted(set([rfc for rfc in rfcs_raw if rfc]))) fecha_inicio = '' fecha_fin = '' if pedimentos_qs.exists(): primer_pedimento = pedimentos_qs.order_by('fecha_pago').first() if primer_pedimento and primer_pedimento.fecha_pago: fecha_inicio = primer_pedimento.fecha_pago.strftime('%Y-%m-%d') ultimo_pedimento = pedimentos_qs.order_by('-fecha_pago').first() if ultimo_pedimento and ultimo_pedimento.fecha_pago: fecha_fin = ultimo_pedimento.fecha_pago.strftime('%Y-%m-%d') # Para cada pedimento, verificar si está completo for pedimento in pedimentos_qs: # Contar documentos de este pedimento docs_pedimento = 0 docs_pendientes_pedimento = 0 # COVES coves_count = Cove.objects.filter(pedimento_id=pedimento.id).count() coves_pendientes = Cove.objects.filter(pedimento_id=pedimento.id, cove_descargado=False).count() docs_pedimento += coves_count docs_pendientes_pedimento += coves_pendientes # PARTIDAS partidas_count = Partida.objects.filter(pedimento_id=pedimento.id).count() partidas_pendientes = Partida.objects.filter(pedimento_id=pedimento.id, descargado=False).count() docs_pedimento += partidas_count docs_pendientes_pedimento += partidas_pendientes # EDOCUMENTS edocs_count = EDocument.objects.filter(pedimento_id=pedimento.id).count() edocs_pendientes = EDocument.objects.filter(pedimento_id=pedimento.id, edocument_descargado=False).count() docs_pedimento += edocs_count docs_pendientes_pedimento += edocs_pendientes # Acumular totales total_documentos += docs_pedimento documentos_sin_descargar += docs_pendientes_pedimento # Si no tiene documentos pendientes, está completo if docs_pendientes_pedimento == 0 and docs_pedimento > 0: pedimentos_completos += 1 # 3. PORCENTAJE porcentaje_faltantes = (documentos_sin_descargar / total_documentos * 100) if total_documentos > 0 else 0 # 4. GENERAR CSV CON DETALLES filename = f"report_{report.id}_{timezone.now().strftime('%Y%m%d%H%M%S')}.csv" file_path = os.path.join(settings.MEDIA_ROOT, 'reports', filename) os.makedirs(os.path.dirname(file_path), exist_ok=True) todas_las_filas = [] # Recopilar datos detallados - UNA FILA POR CADA DOCUMENTO for pedimento in pedimentos_qs: # DATOS BASE DEL PEDIMENTO (se repiten en cada fila) datos_base_pedimento = [ pedimento.aduana or '', pedimento.patente or '', pedimento.regimen or '', pedimento.pedimento or '', # No. Pedimento (7 dígitos) pedimento.pedimento_app or '', # No. Pedimento App completo pedimento.clave_pedimento or '', pedimento.tipo_operacion.tipo if pedimento.tipo_operacion else '', str(pedimento.contribuyente_id) if pedimento.contribuyente_id else '' ] # COVES - Una fila por cada COVE coves = Cove.objects.filter(pedimento_id=pedimento.id) for cove in coves: estado = 'VERDADERO' if cove.cove_descargado else 'FALSO' fila = datos_base_pedimento + [ # str(cove.id), # Identificador de documento cove.numero_cove, 'COVE', # Tipo de documento estado ] todas_las_filas.append(fila) # PARTIDAS - Una fila por cada Partida partidas = Partida.objects.filter(pedimento_id=pedimento.id) for partida in partidas: estado = 'VERDADERO' if partida.descargado else 'FALSO' fila = datos_base_pedimento + [ # str(partida.id), partida.numero_partida, 'PARTIDA', # Tipo de documento estado ] todas_las_filas.append(fila) # EDOCUMENTS - Una fila por cada EDocument edocuments = EDocument.objects.filter(pedimento_id=pedimento.id) for edoc in edocuments: estado = 'VERDADERO' if edoc.edocument_descargado else 'FALSO' fila = datos_base_pedimento + [ # str(edoc.id), edoc.numero_edocument, 'EDOCUMENT', # Tipo de documento estado ] todas_las_filas.append(fila) # 5. ESCRIBIR ARCHIVO CSV with open(file_path, 'w', newline='', encoding='utf-8') as f: writer = csv.writer(f) # SECCIÓN DE TOTALES writer.writerow(['RESUMEN DEL REPORTE - CONTROL DE PEDIMENTOS']) writer.writerow(['ORGANIZACION:', nombre_organizacion]) writer.writerow([]) writer.writerow(['TOTAL DE EXPEDIENTES:', pedimentos_total]) writer.writerow(['TOTAL DE EXPEDIENTES COMPLETOS:', pedimentos_completos]) writer.writerow(['TOTAL DE DOCUMENTOS:', total_documentos]) writer.writerow(['DOCUMENTOS SIN DESCARGAR:', documentos_sin_descargar]) writer.writerow(['PORCENTAJE DE DOCUMENTOS FALTANTES (%):', f"{porcentaje_faltantes:.2f}%"]) writer.writerow(['DESDE: ', fecha_inicio, ' HASTA: ', fecha_fin]) writer.writerow(['LISTA RFC:', rfc_list]) writer.writerow([]) writer.writerow([]) # ENCABEZADOS DE DATOS (según requerimiento) headers = [ 'ADUANA', 'PATENTE', 'REGIMEN', 'NO. PEDIMENTO', 'PEDIMENTO_APP', 'CLAVE_PEDIMENTO', 'TIPO_OPERACION', 'CONTRIBUYENTE_ID', 'IDENTIFICADOR_DOCUMENTO', 'TIPO_DOCUMENTO', 'ESTADO' ] writer.writerow(headers) # DATOS DETALLADOS for fila in todas_las_filas: writer.writerow(fila) with open(file_path, 'rb') as f: report.file.save(filename, ContentFile(f.read()), save=True) report.status = 'ready' report.finished_at = timezone.now() report.save(update_fields=['status', 'file', 'finished_at']) except Exception as e: report.status = 'error' report.error_message = str(e) report.finished_at = timezone.now() report.save(update_fields=['status', 'error_message', 'finished_at'])