Cuser models

This commit is contained in:
2025-10-05 15:44:08 -06:00
parent 0c6dd348e7
commit 5c3df2f34c
6 changed files with 381 additions and 72 deletions

View File

@@ -0,0 +1,20 @@
# Generated by Django 5.2.3 on 2025-10-05 17:47
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cuser', '0003_alter_customuser_rfc'),
('customs', '0015_partida_updated_at'),
]
operations = [
migrations.AlterField(
model_name='customuser',
name='rfc',
field=models.ForeignKey(blank=True, help_text='RFC associated with the user if they are an importer', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='users', to='customs.importador'),
),
]

View File

@@ -12,7 +12,7 @@ class CustomUser(AbstractUser):
profile_picture = models.ImageField(upload_to='profile_pictures/', null=True, blank=True) profile_picture = models.ImageField(upload_to='profile_pictures/', null=True, blank=True)
is_importador = models.BooleanField(default=False, help_text="Indicates if the user is an importer") is_importador = models.BooleanField(default=False, help_text="Indicates if the user is an importer")
rfc = models.CharField(max_length=1, null=True, blank=True, help_text="RFC of the user") rfc = models.ForeignKey('customs.Importador', on_delete=models.SET_NULL, null=True, blank=True, related_name='users', help_text="RFC associated with the user if they are an importer")
def __str__(self): def __str__(self):
return self.username return self.username

View File

@@ -6,7 +6,7 @@ from .views_table import table_summary
urlpatterns = [ urlpatterns = [
path('exportmodel/', ExportModelView.as_view(), name='export-model'), path('exportmodel/', ExportModelView.as_view(), name='export-model'),
path('dashboard/summary/', dashboard_summary, name='dashboard-summary'), path('dashboard/summary/', dashboard_summary, name='dashboard-summary'),
path('documentos-por-fecha/', documentos_por_fecha, name='documentos-por-fecha'), #path('documentos-por-fecha/', documentos_por_fecha, name='documentos-por-fecha'),
#path('table-summary/', table_summary, name='table-summary'), #path('table-summary/', table_summary, name='table-summary'),
] ]

View File

@@ -1,3 +1,4 @@
from warnings import filters
from rest_framework.decorators import api_view, permission_classes from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from api.customs.models import Pedimento, Cove, EDocument, Partida from api.customs.models import Pedimento, Cove, EDocument, Partida
@@ -143,55 +144,50 @@ class ExportModelView(APIView):
class ExportModelView(APIView): class ExportModelView(APIView):
my_tags = ['Reportes'] my_tags = ['Reportes']
permission_classes = [IsAuthenticated & ( permission_classes = [IsAuthenticated & (
IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)] IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
@swagger_auto_schema( @swagger_auto_schema(manual_parameters=[openapi.Parameter('model', openapi.IN_QUERY, description="Nombre del modelo (ejemplo: Registro500)",
manual_parameters=[ type=openapi.TYPE_STRING, required=True)
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,
responses={200: openapi.Response('Campos disponibles', schema=openapi.Schema( properties={
type=openapi.TYPE_OBJECT, 'fields': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Items(type=openapi.TYPE_STRING))
properties={ }
'fields': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Items(type=openapi.TYPE_STRING)) ))}
} )
))} def get(self, request, *args, **kwargs):
) """
def get(self, request, *args, **kwargs): Devuelve los campos disponibles para el modelo solicitado.
""" Ejemplo: /api/reports/exportmodel/?model=Registro500
Devuelve los campos disponibles para el modelo solicitado. """
Ejemplo: /api/reports/exportmodel/?model=Registro500 model_name = request.query_params.get('model')
""" if not model_name:
model_name = request.query_params.get('model') return Response({'error': 'model is required'}, status=status.HTTP_400_BAD_REQUEST)
if not model_name: try:
return Response({'error': 'model is required'}, status=status.HTTP_400_BAD_REQUEST) model = apps.get_model('datastage', model_name)
try: except LookupError:
model = apps.get_model('datastage', model_name) return Response({'error': f'Model {model_name} not found'}, status=status.HTTP_404_NOT_FOUND)
except LookupError: fields = [f.name for f in model._meta.fields]
return Response({'error': f'Model {model_name} not found'}, status=status.HTTP_404_NOT_FOUND) return Response({'fields': fields})
fields = [f.name for f in model._meta.fields]
return Response({'fields': fields})
@swagger_auto_schema( @swagger_auto_schema(request_body=ExportModelSerializer, esponses={200: 'Archivo generado (Excel o CSV)'})
request_body=ExportModelSerializer, def post(self, request, *args, **kwargs):
responses={200: 'Archivo generado (Excel o CSV)'} model_name = request.data.get('model')
) fields = request.data.get('fields')
def post(self, request, *args, **kwargs): filters = request.data.get('filters', {})
model_name = request.data.get('model') filters['organizacion__id'] = self.request.user.organizacion.id if hasattr(request.user, 'organizacion') and request.user.organizacion else None
fields = request.data.get('fields') export_type = request.data.get('type', 'csv')
filters = request.data.get('filters', {}) if not model_name or not fields:
export_type = request.data.get('type', 'csv') return Response({'error': 'model and fields are required'}, status=status.HTTP_400_BAD_REQUEST)
if not model_name or not fields: module = request.data.get('module', 'datastage')
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)
module = request.data.get('module', 'datastage') else:
if export_type == 'excel': return export_model_to_csv(request, model_name, fields, module, filters)
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 # Resumen general para dashboard
@@ -256,29 +252,44 @@ def dashboard_summary(request):
pedimento_ids = list(pedimentos_qs.values_list('id', flat=True)) pedimento_ids = list(pedimentos_qs.values_list('id', flat=True))
coves_total = Cove.objects.filter(pedimento_id__in=pedimento_ids).count() 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() coves_procesados = Cove.objects.filter(
acuse_coves_procesados = Cove.objects.filter(pedimento_id__in=pedimento_ids, acuse_cove_descargado=True).count() 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 acuse_coves_pendientes = coves_total - acuse_coves_procesados
coves_pendientes = coves_total - coves_procesados coves_pendientes = coves_total - coves_procesados
edocs_total = EDocument.objects.filter(pedimento_id__in=pedimento_ids).count() edocs_total = EDocument.objects.filter(
edocs_descargados = EDocument.objects.filter(pedimento_id__in=pedimento_ids, edocument_descargado=True).count() pedimento_id__in=pedimento_ids).count()
acuse_descargados = EDocument.objects.filter(pedimento_id__in=pedimento_ids, acuse_descargado=True).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 edocs_pendientes = edocs_total - edocs_descargados
acuses_pendientes = edocs_total - acuse_descargados acuses_pendientes = edocs_total - acuse_descargados
remesas_total = Document.objects.filter(document_type__id=3, pedimento_id__in=pedimento_ids).count() remesas_total = Document.objects.filter(
documentos_descargados = Document.objects.filter(pedimento_id__in=pedimento_ids).count() document_type__id=3, pedimento_id__in=pedimento_ids).count()
partidas_total = Partida.objects.filter(pedimento_id__in=pedimento_ids).count() documentos_descargados = Document.objects.filter(
partidas_descargadas = Partida.objects.filter(pedimento_id__in=pedimento_ids, descargado=True).count() 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 partidas_pendientes = partidas_total - partidas_descargadas
# Indicadores de cumplimiento # Indicadores de cumplimiento
cumplimiento_pedimentos = (pedimentos_completos / pedimentos_total * 100) if pedimentos_total else 0 cumplimiento_pedimentos = (
cumplimiento_acuse_coves = (acuse_coves_procesados / coves_total * 100) if coves_total else 0 pedimentos_completos / pedimentos_total * 100) if pedimentos_total else 0
cumplimiento_coves = (coves_procesados / coves_total * 100) if coves_total else 0 cumplimiento_acuse_coves = (
cumplimiento_edocs = (edocs_descargados / edocs_total * 100) if edocs_total else 0 acuse_coves_procesados / coves_total * 100) if coves_total else 0
cumplimiento_acuses = (acuse_descargados / edocs_total * 100) if edocs_total else 0 cumplimiento_coves = (
cumplimiento_partidas = (partidas_descargadas / partidas_total * 100) if partidas_total else 0 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
return Response({ return Response({
"pedimentos": { "pedimentos": {
@@ -301,7 +312,7 @@ def dashboard_summary(request):
"edocs_descargados": edocs_descargados, "edocs_descargados": edocs_descargados,
"edocs_pendientes": edocs_pendientes, "edocs_pendientes": edocs_pendientes,
"acuse_descargados": acuse_descargados, "acuse_descargados": acuse_descargados,
"acuses_pendientes": acuses_pendientes, "acuses_pendientes": acuses_pendientes,
"edocs_cumplimiento": round(cumplimiento_edocs, 2), "edocs_cumplimiento": round(cumplimiento_edocs, 2),
"acuses_cumplimiento": round(cumplimiento_acuses, 2) "acuses_cumplimiento": round(cumplimiento_acuses, 2)
}, },
@@ -314,7 +325,7 @@ def dashboard_summary(request):
"partidas": { "partidas": {
"total": partidas_total, "total": partidas_total,
"partidas_descargadas": partidas_descargadas, "partidas_descargadas": partidas_descargadas,
"partidas_pendientes": partidas_pendientes, "partidas_pendientes": partidas_pendientes,
"cumplimiento": round(cumplimiento_partidas, 2) "cumplimiento": round(cumplimiento_partidas, 2)
} }
}) })

278
api/reports/views_new.py Normal file
View File

@@ -0,0 +1,278 @@
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
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 rest_framework.views import APIView
from core.permissions import (
IsSameOrganization,
IsSameOrganizationDeveloper,
IsSameOrganizationAndAdmin,
IsSuperUser
)
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')
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)
# Aplicar filtro de organización
filters = {"organizacion__id": request.user.organizacion.id}
# Agregar filtros adicionales del request
filters.update(request.data.get('filters', {}))
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)
@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
return Response({
"pedimentos": {
"total": pedimentos_total,
"completos": pedimentos_completos,
"pendientes": pedimentos_pendientes,
"cumplimiento": round(cumplimiento_pedimentos, 2)
},
"coves": {
"total": coves_total,
"coves_procesados": coves_procesados,
"acuse_coves_procesados": acuse_coves_procesados,
"coves_pendientes": coves_pendientes,
"acuse_coves_pendientes": acuse_coves_pendientes,
"coves_cumplimiento": round(cumplimiento_coves, 2),
"acuse_coves_cumplimiento": round(cumplimiento_acuse_coves, 2)
},
"edocuments": {
"total": edocs_total,
"edocs_descargados": edocs_descargados,
"edocs_pendientes": edocs_pendientes,
"acuse_descargados": acuse_descargados,
"acuses_pendientes": acuses_pendientes,
"edocs_cumplimiento": round(cumplimiento_edocs, 2),
"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)
}
})

View File

@@ -5,7 +5,7 @@ class FiltroPorOrganizacionMixin:
model = None model = None
campo_usuario = 'user' campo_usuario = 'user'
campo_organizacion = 'organizacion' campo_organizacion = 'organizacion'
campo_rfc = 'rfc' campo_rfc = 'rfc__id'
campo_contribuyente = 'pedimento__contribuyente' # solo si aplica campo_contribuyente = 'pedimento__contribuyente' # solo si aplica
def get_queryset_filtrado(self): def get_queryset_filtrado(self):
@@ -61,10 +61,10 @@ class OrganizacionFiltradaMixin:
if 'Agente Aduanal' in grupos: if 'Agente Aduanal' in grupos:
return model.objects.filter(**filtros_base) return model.objects.filter(**filtros_base)
if hasattr(model, self.campo_contribuyente): # if hasattr(model, self.campo_contribuyente):
if self.request.user.is_authenticated and'Importador' in grupos and getattr(self.request.user, 'is_importador', False): if self.request.user.is_authenticated and 'Importador' in grupos :
filtros_base[f"{self.campo_contribuyente}"] = self.request.user.rfc filtros_base[f"{self.campo_contribuyente}__rfc"] = self.request.user.rfc.rfc
return model.objects.filter(**filtros_base) return model.objects.filter(**filtros_base)
# Si no entra en los roles válidos # Si no entra en los roles válidos
return model.objects.none() return model.objects.none()