from warnings import filters from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated from api.customs.models import Pedimento, Cove, EDocument, Partida from api.record.models import Document from api.organization.models import Organizacion from django.db.models import Count, Q # Registrar endpoint en urls.py: # path('dashboard/summary/', dashboard_summary) import csv import io from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi from .serializers import ExportModelSerializer from rest_framework.response import Response from django.http import HttpResponse import openpyxl from django.apps import apps from rest_framework import status from django.shortcuts import render from rest_framework import viewsets from .serializers import ExportModelSerializer from core.permissions import ( IsSameOrganization, IsSameOrganizationDeveloper, IsSameOrganizationAndAdmin, IsSuperUser ) from rest_framework.permissions import IsAuthenticated import csv import io import openpyxl from django.http import HttpResponse from django.apps import apps from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi from rest_framework.permissions import IsAuthenticated from core.permissions import ( IsSameOrganization, IsSameOrganizationDeveloper, IsSameOrganizationAndAdmin, IsSuperUser ) from .serializers import ExportModelSerializer def export_model_to_csv(request, model_name, fields, module='datastage', filters=None): model = apps.get_model(module, model_name) queryset = model.objects.filter(**(filters or {})).values(*fields) response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = f'attachment; filename="{model_name}.csv"' writer = csv.DictWriter(response, fieldnames=fields) writer.writeheader() for row in queryset: writer.writerow(row) return response def export_model_to_excel(request, model_name, fields, module='datastage', filters=None): model = apps.get_model(module, model_name) queryset = model.objects.filter(**(filters or {})).values(*fields) wb = openpyxl.Workbook() ws = wb.active ws.append(fields) for row in queryset: # Convertir cada valor a string para asegurar compatibilidad con Excel row_values = [] for field in fields: value = row[field] # Si es UUID u otro objeto, convertirlo a string if hasattr(value, '__str__'): value = str(value) row_values.append(value) ws.append(row_values) output = io.BytesIO() wb.save(output) output.seek(0) response = HttpResponse(output.read( ), content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') response['Content-Disposition'] = f'attachment; filename="{model_name}.xlsx"' return response class ExportModelView(APIView): my_tags = ['Reportes'] permission_classes = [IsAuthenticated & ( IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)] @swagger_auto_schema( manual_parameters=[ openapi.Parameter('model', openapi.IN_QUERY, description="Nombre del modelo (ejemplo: Registro500)", type=openapi.TYPE_STRING, required=True) ], responses={200: openapi.Response('Campos disponibles', schema=openapi.Schema( type=openapi.TYPE_OBJECT, properties={ 'fields': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Items(type=openapi.TYPE_STRING)) } ))} ) def get(self, request, *args, **kwargs): """ Devuelve los campos disponibles para el modelo solicitado. Ejemplo: /api/reports/exportmodel/?model=Registro500 """ model_name = request.query_params.get('model') module = request.query_params.get('module', 'datastage') if not model_name: return Response({'error': 'model is required'}, status=status.HTTP_400_BAD_REQUEST) try: model = apps.get_model(module, model_name) except LookupError: return Response({'error': f'Model {model_name} not found in app {module}'}, status=status.HTTP_404_NOT_FOUND) fields = [f.name for f in model._meta.fields] return Response({'fields': fields}) @swagger_auto_schema( request_body=ExportModelSerializer, responses={200: 'Archivo generado (Excel o CSV)'} ) def post(self, request, *args, **kwargs): model_name = request.data.get('model') fields = request.data.get('fields') filters = request.data.get('filters', {}) export_type = request.data.get('type', 'csv') module = request.data.get('module', 'datastage') if not model_name or not fields: return Response({'error': 'model and fields are required'}, status=status.HTTP_400_BAD_REQUEST) if export_type == 'excel': return export_model_to_excel(request, model_name, fields, module, filters) else: return export_model_to_csv(request, model_name, fields, module, filters) # Create your views here. class ExportModelView(APIView): my_tags = ['Reportes'] permission_classes = [IsAuthenticated & ( IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)] @swagger_auto_schema(request_body=ExportModelSerializer, esponses={200: 'Archivo generado (Excel o CSV)'}) def post(self, request, *args, **kwargs): model_name = request.data.get('model') fields = request.data.get('fields') filters = request.data.get('filters', {}) filters['organizacion__id'] = self.request.user.organizacion.id if hasattr(request.user, 'organizacion') and request.user.organizacion else None export_type = request.data.get('type', 'csv') if not model_name or not fields: return Response({'error': 'model and fields are required'}, status=status.HTTP_400_BAD_REQUEST) module = request.data.get('module', 'datastage') if export_type == 'excel': return export_model_to_excel(request, model_name, fields, module, filters) else: return export_model_to_csv(request, model_name, fields, module, filters) # Resumen general para dashboard @api_view(['GET']) @permission_classes([ IsAuthenticated ]) def dashboard_summary(request): org_id = request.query_params.get('organizacion_id') filters = {} user = request.user pedimento_app = request.query_params.get('pedimento_app') aduana = request.query_params.get('aduana') patente = request.query_params.get('patente') regimen = request.query_params.get('regimen') agente_aduanal = request.query_params.get('agente_aduanal') tipo_operacion = request.query_params.get('tipo_operacion') fecha_pago_gte = request.query_params.get('fecha_pago__gte') fecha_pago_lte = request.query_params.get('fecha_pago__lte') contribuyente__rfc = request.query_params.get('contribuyente__rfc') # Si no se especifica organización y el usuario tiene organización, usarla if not org_id and hasattr(user, 'organizacion') and user.organizacion: org_id = user.organizacion.id # Si no es superusuario, filtrar por organización if org_id and not getattr(user, 'is_superuser', False): filters['organizacion_id'] = org_id # Si el usuario pertenece al grupo Importador, filtrar por RFC if user.groups.filter(name='Importador').exists(): rfc = getattr(user, 'rfc', None) if rfc: filters['contribuyente__rfc'] = rfc if pedimento_app: filters['pedimento_app'] = pedimento_app if aduana: filters['aduana'] = aduana if patente: filters['patente'] = patente if regimen: filters['regimen'] = regimen if agente_aduanal: filters['agente_aduanal'] = agente_aduanal if tipo_operacion: filters['tipo_operacion__tipo'] = tipo_operacion if fecha_pago_gte: filters['fecha_pago__gte'] = fecha_pago_gte if fecha_pago_lte: filters['fecha_pago__lte'] = fecha_pago_lte if contribuyente__rfc: filters['contribuyente__rfc'] = contribuyente__rfc # Filtrar pedimentos pedimentos_qs = Pedimento.objects.filter(**filters) pedimentos_total = pedimentos_qs.count() pedimentos_completos = pedimentos_qs.filter(existe_expediente=True).count() pedimentos_pendientes = pedimentos_total - pedimentos_completos # Usar los IDs de pedimentos filtrados para los demás modelos pedimento_ids = list(pedimentos_qs.values_list('id', flat=True)) coves_total = Cove.objects.filter(pedimento_id__in=pedimento_ids).count() coves_procesados = Cove.objects.filter( pedimento_id__in=pedimento_ids, cove_descargado=True).count() acuse_coves_procesados = Cove.objects.filter( pedimento_id__in=pedimento_ids, acuse_cove_descargado=True).count() acuse_coves_pendientes = coves_total - acuse_coves_procesados coves_pendientes = coves_total - coves_procesados edocs_total = EDocument.objects.filter( pedimento_id__in=pedimento_ids).count() edocs_descargados = EDocument.objects.filter( pedimento_id__in=pedimento_ids, edocument_descargado=True).count() acuse_descargados = EDocument.objects.filter( pedimento_id__in=pedimento_ids, acuse_descargado=True).count() edocs_pendientes = edocs_total - edocs_descargados acuses_pendientes = edocs_total - acuse_descargados remesas_total = Document.objects.filter( document_type__id=3, pedimento_id__in=pedimento_ids).count() documentos_descargados = Document.objects.filter( pedimento_id__in=pedimento_ids).count() partidas_total = Partida.objects.filter( pedimento_id__in=pedimento_ids).count() partidas_descargadas = Partida.objects.filter( pedimento_id__in=pedimento_ids, descargado=True).count() partidas_pendientes = partidas_total - partidas_descargadas # Indicadores de cumplimiento cumplimiento_pedimentos = ( pedimentos_completos / pedimentos_total * 100) if pedimentos_total else 0 cumplimiento_acuse_coves = ( acuse_coves_procesados / coves_total * 100) if coves_total else 0 cumplimiento_coves = ( coves_procesados / coves_total * 100) if coves_total else 0 cumplimiento_edocs = (edocs_descargados / edocs_total * 100) if edocs_total else 0 cumplimiento_acuses = (acuse_descargados / edocs_total * 100) if edocs_total else 0 cumplimiento_partidas = (partidas_descargadas / partidas_total * 100) if partidas_total else 0 # Calcular cumplimiento total (promedio de todos los indicadores) indicadores = [ cumplimiento_pedimentos, cumplimiento_coves, cumplimiento_acuse_coves, cumplimiento_edocs, cumplimiento_acuses, cumplimiento_partidas ] cumplimiento_total = sum(indicadores) / len(indicadores) if indicadores else 0 return Response({ "cumplimiento_total": round(cumplimiento_total, 2), "pedimentos": { "total": pedimentos_total, "completos": pedimentos_completos, "pendientes": pedimentos_pendientes, "cumplimiento": round(cumplimiento_pedimentos, 2) }, "coves": { "total": coves_total, "coves_procesados": coves_procesados, "coves_pendientes": coves_pendientes, "coves_cumplimiento": round(cumplimiento_coves, 2), }, "acuse_coves": { "total": coves_total, "acuse_coves_procesados": acuse_coves_procesados, "acuse_coves_pendientes": acuse_coves_pendientes, "acuse_coves_cumplimiento": round(cumplimiento_acuse_coves, 2) }, "edocuments": { "total": edocs_total, "edocs_descargados": edocs_descargados, "edocs_pendientes": edocs_pendientes, "edocs_cumplimiento": round(cumplimiento_edocs, 2), }, "acuses":{ "total": edocs_total, "acuse_descargados": acuse_descargados, "acuses_pendientes": acuses_pendientes, "acuses_cumplimiento": round(cumplimiento_acuses, 2) }, "remesas": { "total": remesas_total }, "documentos": { "descargados": documentos_descargados }, "partidas": { "total": partidas_total, "partidas_descargadas": partidas_descargadas, "partidas_pendientes": partidas_pendientes, "cumplimiento": round(cumplimiento_partidas, 2) } })