feature/rbac permisos y roles implementados

This commit is contained in:
2026-05-21 07:54:59 -06:00
parent 9bbed42cf3
commit a318b70324
38 changed files with 2596 additions and 901 deletions

View File

@@ -10,12 +10,20 @@ from rest_framework.views import APIView
from rest_framework.exceptions import PermissionDenied
from rest_framework import status
from django_filters.rest_framework import DjangoFilterBackend
from django.http import HttpResponse
import django_filters
import io
import openpyxl
from rest_framework.filters import SearchFilter, OrderingFilter
from core.permissions import (
IsSameOrganization,
IsSameOrganization,
IsSameOrganizationDeveloper,
IsSameOrganizationAndAdmin,
IsSuperUser
IsSuperUser,
get_org_context,
require_permission,
user_has_permission,
is_internal_service_request,
)
from api.customs.models import (
Pedimento,
@@ -244,6 +252,19 @@ class PedimentoPagination(PageNumberPagination):
return super().paginate_queryset(queryset, request, view)
# Create your views here.
class PedimentoFilter(django_filters.FilterSet):
# Rango de fecha de pago: ?fecha_pago_desde=YYYY-MM-DD&fecha_pago_hasta=YYYY-MM-DD
fecha_pago_desde = django_filters.DateFilter(field_name='fecha_pago', lookup_expr='gte')
fecha_pago_hasta = django_filters.DateFilter(field_name='fecha_pago', lookup_expr='lte')
class Meta:
model = Pedimento
fields = [
'patente', 'aduana', 'tipo_operacion', 'clave_pedimento',
'pedimento', 'existe_expediente', 'contribuyente',
'curp_apoderado', 'fecha_pago', 'pedimento_app',
]
class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltradaMixin): # Pendiente de permisos de creacion
"""
ViewSet for Pedimento model.
@@ -257,53 +278,124 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
- existe_expediente: Filtro por expediente (True/False)
- contribuyente: Filtro por contribuyente
- curp_apoderado: Filtro por curp del apoderado
- fecha_pago: Filtro por fecha de pago (YYYY-MM-DD)
- fecha_pago: Filtro por fecha de pago exacta (YYYY-MM-DD)
- fecha_pago_desde: Rango inicio de fecha de pago (YYYY-MM-DD)
- fecha_pago_hasta: Rango fin de fecha de pago (YYYY-MM-DD)
- patente: Filtro por patente
- aduana: Filtro por aduana
- tipo_operacion: Filtro por tipo de operación
- clave_pedimento: Filtro por clave de pedimento
- ordering: Ordenar por campo (ej: -created_at, pedimento)
Ejemplos:
- /pedimentos/ → Devuelve TODOS los pedimentos
- /pedimentos/?page_size=10 → Devuelve los primeros 10
- /pedimentos/?page_size=10&page=2 → Devuelve los pedimentos 11-20
- /pedimentos/?pedimento=12345678 → Filtra por número de pedimento
- /pedimentos/?existe_expediente=true → Filtra por expediente existente
- /pedimentos/?contribuyente=EMPRESA → Filtra por contribuyente
- /pedimentos/?curp_apoderado=XXXX → Filtra por curp apoderado
- /pedimentos/?fecha_pago=2025-07-18 → Filtra por fecha de pago
- /pedimentos/?fecha_pago_desde=2025-01-01&fecha_pago_hasta=2025-12-31 → Rango de fechas
- /pedimentos/export-excel/?contribuyente=EMPRESA → Descarga Excel con filtros
"""
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
serializer_class = PedimentoSerializer
pagination_class = PedimentoPagination
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
model = Pedimento
filterset_fields = ['patente', 'aduana', 'tipo_operacion', 'clave_pedimento', 'pedimento', 'existe_expediente', 'contribuyente', 'curp_apoderado', 'fecha_pago', 'pedimento_app']
filterset_class = PedimentoFilter
search_fields = ['pedimento', 'pedimento_app', 'agente_aduanal', 'clave_pedimento']
# AGREGAR ESTOS CAMPOS PARA ORDENACIÓN
ordering_fields = ['created_at', 'pedimento', 'fecha_pago', 'aduana', 'patente']
ordering = ['-created_at'] # Orden descendente por fecha de creación por defecto
ordering = ['-created_at']
def get_permissions(self):
perms = {
'list': 'pedimentos.view',
'retrieve': 'pedimentos.view',
'create': 'pedimentos.create',
'update': 'pedimentos.edit',
'partial_update': 'pedimentos.edit',
'destroy': 'pedimentos.delete',
'procesar_completo': 'pedimentos.process',
'procesar_partidas': 'pedimentos.process',
'procesar_coves': 'pedimentos.process',
'procesar_acuse_coves': 'pedimentos.process',
'procesar_edocs': 'pedimentos.process',
'procesar_acuses': 'pedimentos.process',
'procesar_remesas': 'pedimentos.process',
'bulk_delete': 'pedimentos.delete',
'bulk_create': 'pedimentos.create',
'bulk_create_pedimento_desk': 'pedimentos.create',
'bulk_upload_record': 'documentos.upload',
'bulk_upload_record_async': 'documentos.upload',
}
codename = perms.get(self.action, 'pedimentos.view')
return [IsAuthenticated(), require_permission(codename)()]
def get_queryset(self):
queryset = self.get_queryset_filtrado_por_organizacion() # Tambien filtra por importador
if not user_has_permission(self.request.user, 'pedimentos.view'):
return Pedimento.objects.none()
return self.get_queryset_filtrado_por_organizacion()
# pedimento_app_filter = self.request.GET.get('pedimento_app', None)
@action(detail=False, methods=['get'], url_path='export-excel')
def export_excel(self, request):
"""Exporta a Excel todos los pedimentos que coincidan con los filtros activos."""
queryset = self.filter_queryset(self.get_queryset())
# if pedimento_app_filter:
# print(f"Filtro por pedimento_app: {pedimento_app_filter}")
# queryset = queryset.filter(pedimento_app__icontains=pedimento_app_filter)
columnas = [
('pedimento_app', 'Pedimento'),
('fecha_pago', 'Fecha Pago'),
('aduana', 'Aduana'),
('patente', 'Patente'),
('contribuyente', 'Contribuyente'),
('curp_apoderado','CURP Apoderado'),
('numero_partidas','Partidas'),
('created_at', 'F. Carga'),
('tipo_operacion','Tipo Op.'),
('clave_pedimento','Clave Pedimento'),
('documentos_count', 'Archivos'),
('existe_expediente','Expediente'),
]
return queryset
def safe_value(val):
if val is None:
return ''
if isinstance(val, bool):
return '' if val else 'No'
if isinstance(val, (int, float)):
return val
if isinstance(val, (datetime, date)):
return str(val)[:10]
# ForeignKey instances u otros objetos Django → su representación string
return str(val)
wb = openpyxl.Workbook()
ws = wb.active
ws.title = 'Pedimentos'
ws.append([label for _, label in columnas])
for ped in queryset.iterator():
fila = []
for campo, _ in columnas:
val = getattr(ped, campo, None)
fila.append(safe_value(val))
ws.append(fila)
# Autoajuste de ancho de columnas
for col in ws.columns:
max_len = max((len(str(cell.value or '')) for cell in col), default=10)
ws.column_dimensions[col[0].column_letter].width = min(max_len + 2, 50)
output = io.BytesIO()
wb.save(output)
output.seek(0)
filename = f"pedimentos_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
response = HttpResponse(
output.read(),
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
)
response['Content-Disposition'] = f'attachment; filename="{filename}"'
return response
def perform_create(self, serializer):
"""
Asigna automáticamente la organización del usuario autenticado al crear un pedimento.
"""
if not self.request.user.is_authenticated or not hasattr(self.request.user, 'organizacion'):
raise ValueError("Usuario no autenticado o sin organización")
org = get_org_context(self.request.user)
data = serializer.validated_data
if not data.get('pedimento_app'):
fecha_pago = data.get('fecha_pago')
@@ -312,7 +404,7 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
pedimento = data.get('pedimento')
if fecha_pago and aduana and patente and pedimento:
pedimento_app = f"{str(fecha_pago.year)[-2:]}-{str(aduana).zfill(2)[-2:]}-{str(patente).zfill(4)[-4:]}-{str(pedimento).zfill(7)[-7:]}"
serializer.save(organizacion=self.request.user.organizacion, pedimento_app=pedimento_app)
serializer.save(organizacion=org, pedimento_app=pedimento_app)
try:
# Usar el nombre del servicio de Docker Compose en lugar de localhost
@@ -375,6 +467,9 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
]
}
def perform_destroy(self, instance):
instance.delete()
@action(detail=True, methods=['post'], url_path='procesar-completo')
def procesar_completo(self, request, pk=None):
"""
@@ -2197,33 +2292,70 @@ class PartidaViewSet(viewsets.ModelViewSet):
Permite filtrar por:
- pedimento: UUID del pedimento (query parameter principal)
- pedimento__id: UUID del pedimento (alternativo)
Ejemplo: GET /api/partidas/?pedimento=6782d22e-5e97-4efc-87c9-bd8497c8ac7e
"""
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
queryset = Partida.objects.all()
serializer_class = PartidaSerializer
pagination_class = CustomPagination
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_fields = {
'pedimento': ['exact'], # Filtro directo por UUID del pedimento
'pedimento__id': ['exact'], # Filtro alternativo
'numero_partida': ['exact', 'gte', 'lte'], # Filtros por número de partida
'descargado': ['exact'], # Filtro por estado de descarga
'created_at': ['exact', 'gte', 'lte'], # Filtros por fecha de creación
'updated_at': ['exact', 'gte', 'lte'] # Filtros por fecha de actualización
'pedimento': ['exact'],
'pedimento__id': ['exact'],
'numero_partida': ['exact', 'gte', 'lte'],
'descargado': ['exact'],
'created_at': ['exact', 'gte', 'lte'],
'updated_at': ['exact', 'gte', 'lte'],
}
search_fields = ['pedimento__pedimento', 'pedimento__pedimento_app']
ordering_fields = ['numero_partida', 'pedimento__pedimento', 'id', 'created_at', 'updated_at']
ordering = ['numero_partida'] # Ordenar por número de partida por defecto
ordering = ['numero_partida']
my_tags = ['Partidas']
def get_permissions(self):
perms = {
'list': 'partidas.view',
'retrieve': 'partidas.view',
'create': 'partidas.create',
'update': 'partidas.edit',
'partial_update': 'partidas.edit',
'destroy': 'partidas.delete',
'bulk_delete_partidas_vu': 'partidas.delete',
}
codename = perms.get(self.action, 'partidas.view')
return [IsAuthenticated(), require_permission(codename)()]
def get_queryset(self):
user = self.request.user
if is_internal_service_request(self.request):
return Partida.objects.all()
if not user_has_permission(user, 'partidas.view'):
return Partida.objects.none()
org = get_org_context(user)
if not org:
return Partida.objects.none()
qs = Partida.objects.filter(pedimento__organizacion=org)
if user.is_importador:
qs = qs.filter(pedimento__contribuyente__in=user.rfc.all())
return qs
def perform_create(self, serializer):
if is_internal_service_request(self.request):
serializer.save()
return
pedimento = serializer.validated_data.get('pedimento')
org = get_org_context(self.request.user)
if pedimento and pedimento.organizacion != org:
raise PermissionDenied("El pedimento no pertenece a tu organización.")
serializer.save()
def perform_destroy(self, instance):
instance.delete()
class ViewSetTipoOperacion(LoggingMixin, viewsets.ModelViewSet):
"""
ViewSet for TipoOperacion model.
"""
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
permission_classes = [IsAuthenticated, require_permission('pedimentos.view')]
queryset = TipoOperacion.objects.all()
serializer_class = TipoOperacionSerializer
@@ -2233,9 +2365,17 @@ class ViewSetTipoOperacion(LoggingMixin, viewsets.ModelViewSet):
search_fields = ['tipo', 'descripcion']
ordering_fields = ['tipo', 'descripcion']
ordering = ['tipo']
my_tags = ['Tipos_Operacion']
def get_queryset(self):
if is_internal_service_request(self.request):
return TipoOperacion.objects.all()
org = get_org_context(self.request.user)
if not org:
return TipoOperacion.objects.none()
return TipoOperacion.objects.filter(organizacion=org)
def perform_create(self, serializer):
"""
Asigna automáticamente la organización del usuario autenticado al crear un tipo de operación.
@@ -2276,7 +2416,6 @@ class ViewSetProcesamientoPedimento(viewsets.ModelViewSet, ProcesosPorOrganizaci
- /procesamientopedimentos/ → Devuelve TODOS los procesamientos
- /procesamientopedimentos/?page_size=5 → Devuelve los primeros 5
"""
permission_classes = [IsAuthenticated, IsSuperUser | IsSameOrganizationDeveloper ]
serializer_class = ProcesamientoPedimentoSerializer
pagination_class = CustomPagination
model = ProcesamientoPedimento
@@ -2291,60 +2430,61 @@ class ViewSetProcesamientoPedimento(viewsets.ModelViewSet, ProcesosPorOrganizaci
search_fields = ['pedimento__pedimento_app', 'pedimento__pedimento']
ordering_fields = ['created_at', 'updated_at']
ordering = ['-created_at']
def get_queryset(self):
return self.get_queryset_filtrado_por_organizacion()
def perform_create(self, serializer):
"""
Asigna siempre la organización al crear un procesamiento de pedimento.
- Para superusuarios: requiere que la organización venga explícitamente en los datos validados.
- Para usuarios normales: asigna la organización del usuario autenticado.
"""
user = self.request.user
if not user.is_authenticated:
raise ValueError("Usuario no autenticado")
# Si es superusuario, debe venir la organización en los datos validados
if user.is_superuser:
organizacion = serializer.validated_data.get('organizacion', None)
if not organizacion:
raise ValueError("El superusuario debe especificar una organización al crear el procesamiento de pedimento.")
def get_permissions(self):
perms = {
'list': 'pedimentos.view',
'retrieve': 'pedimentos.view',
'create': 'pedimentos.process',
'update': 'pedimentos.process',
'partial_update': 'pedimentos.process',
'destroy': 'pedimentos.process',
}
codename = perms.get(self.action, 'pedimentos.view')
return [IsAuthenticated(), require_permission(codename)()]
def get_queryset(self):
user = self.request.user
if is_internal_service_request(self.request):
return ProcesamientoPedimento.objects.all()
if not user_has_permission(user, 'pedimentos.view'):
return ProcesamientoPedimento.objects.none()
org = get_org_context(user)
if not org:
return ProcesamientoPedimento.objects.none()
if user.is_importador:
return ProcesamientoPedimento.objects.filter(
organizacion=org,
pedimento__contribuyente__in=user.rfc.all()
)
return ProcesamientoPedimento.objects.filter(organizacion=org)
def perform_create(self, serializer):
if is_internal_service_request(self.request):
serializer.save()
return
# Para usuarios normales, asignar siempre la organización del usuario
if not hasattr(user, 'organizacion') or not user.organizacion:
raise ValueError("Usuario sin organización")
serializer.save(organizacion=user.organizacion)
org = get_org_context(self.request.user)
if not org:
raise PermissionDenied("Sin organización activa.")
serializer.save(organizacion=org)
def perform_update(self, serializer):
"""
Permite actualizar un procesamiento de pedimento, pero solo si el usuario es superusuario o pertenece a la misma organización.
"""
if not self.request.user.is_authenticated:
raise ValueError("Usuario no autenticado")
if self.request.user.is_superuser:
if is_internal_service_request(self.request):
serializer.save()
return
if (self.request.user.groups.filter(name='developer').exists() or self.request.user.groups.filter(name='admin').exists() or self.request.user.groups.filter(name='user').exists()) and self.request.user.groups.filter(name='Agente Aduanal').exists():
# Para usuarios normales, usar siempre su organización
if not hasattr(self.request.user, 'organizacion') or not self.request.user.organizacion:
raise ValueError("Usuario sin organización")
serializer.save(organizacion=self.request.user.organizacion)
return
if not user_has_permission(self.request.user, 'pedimentos.process'):
raise PermissionDenied("Se requiere el permiso pedimentos.process.")
org = get_org_context(self.request.user)
if not org:
raise PermissionDenied("Sin organización activa.")
serializer.save(organizacion=org)
raise ValueError("Usuario no autenticado o sin permisos para actualizar ProcesamientoPedimento")
my_tags = ['Procesamientos_Pedimentos']
class ViewSetEDocument(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltradaMixin):
"""
ViewSet for EDocument model.
"""
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
serializer_class = EDocumentSerializer
pagination_class = CustomPagination
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
@@ -2353,60 +2493,48 @@ class ViewSetEDocument(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
ordering_fields = ['created_at', 'updated_at', 'numero_edocument']
ordering = ['-created_at']
model = EDocument
campo_contribuyente = 'pedimento__contribuyente'
my_tags = ['EDocuments']
def get_permissions(self):
perms = {
'list': 'edocuments.view',
'retrieve': 'edocuments.view',
'create': 'edocuments.create',
'update': 'edocuments.edit',
'partial_update': 'edocuments.edit',
'destroy': 'edocuments.delete',
'bulk_delete_edocs_vu': 'edocuments.delete',
}
codename = perms.get(self.action, 'edocuments.view')
return [IsAuthenticated(), require_permission(codename)()]
def get_queryset(self):
if not user_has_permission(self.request.user, 'edocuments.view'):
return EDocument.objects.none()
return self.get_queryset_filtrado_por_organizacion()
def perform_create(self, serializer):
"""
Asigna automáticamente la organización del usuario autenticado al crear un EDocument.
Para superusuarios, permite especificar una organización diferente.
"""
if not self.request.user.is_authenticated:
raise ValueError("Usuario no autenticado")
# Si es superusuario y se especifica organizacion en los datos validados
if self.request.user.is_superuser:
# Permitir que el superusuario especifique la organización
if is_internal_service_request(self.request):
serializer.save()
return
print(f"self.request.user.groups >>>> {self.request.user.groups}")
if (self.request.user.groups.filter(name='developer').exists() or self.request.user.groups.filter(name='admin').exists() or self.request.user.groups.filter(name='user').exists()) and self.request.user.groups.filter(name='Agente Aduanal').exists():
# Para usuarios normales, usar siempre su organización
if not hasattr(self.request.user, 'organizacion') or not self.request.user.organizacion:
raise ValueError("Usuario sin organización")
serializer.save(organizacion=self.request.user.organizacion)
return
raise ValueError("Usuario no autenticado o sin permisos para crear EDocument")
org = get_org_context(self.request.user)
serializer.save(organizacion=org)
def perform_update(self, serializer):
"""
Permite actualizar un EDocument, pero solo si el usuario es superusuario o pertenece a la misma organización.
"""
if not self.request.user.is_authenticated:
raise ValueError("Usuario no autenticado")
# Si es superusuario, permite actualizar sin restricciones
if self.request.user.is_superuser:
if is_internal_service_request(self.request):
serializer.save()
return
if (self.request.user.groups.filter(name='developer').exists() or self.request.user.groups.filter(name='admin').exists() or self.request.user.groups.filter(name='user').exists()) and self.request.user.groups.filter(name='Agente Aduanal').exists():
# Para usuarios normales, usar siempre su organización
if not hasattr(self.request.user, 'organizacion') or not self.request.user.organizacion:
raise ValueError("Usuario sin organización")
serializer.save(organizacion=self.request.user.organizacion)
org = get_org_context(self.request.user)
serializer.save(organizacion=org)
raise ValueError("Usuario no autenticado o sin permisos para actualizar EDocument")
def perform_destroy(self, instance):
instance.delete()
class ViewSetCove(viewsets.ModelViewSet, OrganizacionFiltradaMixin):
"""
ViewSet for Cove model.
"""
permission_classes = [IsAuthenticated & (IsSuperUser |IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper )]
serializer_class = CoveSerializer
pagination_class = CustomPagination
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
@@ -2415,61 +2543,48 @@ class ViewSetCove(viewsets.ModelViewSet, OrganizacionFiltradaMixin):
ordering_fields = ['created_at', 'updated_at', 'numero_cove']
ordering = ['-created_at']
model = Cove
campo_contribuyente = 'pedimento__contribuyente'
my_tags = ['Coves']
def get_permissions(self):
perms = {
'list': 'coves.view',
'retrieve': 'coves.view',
'create': 'coves.create',
'update': 'coves.edit',
'partial_update': 'coves.edit',
'destroy': 'coves.delete',
'bulk_delete_coves_vu': 'coves.delete',
}
codename = perms.get(self.action, 'coves.view')
return [IsAuthenticated(), require_permission(codename)()]
def get_queryset(self):
if not user_has_permission(self.request.user, 'coves.view'):
return Cove.objects.none()
return self.get_queryset_filtrado_por_organizacion()
def perform_create(self, serializer):
"""
Asigna automáticamente la organización del usuario autenticado al crear un Cove.
Para superusuarios, permite especificar una organización diferente.
"""
if not self.request.user.is_authenticated:
raise ValueError("Usuario no autenticado")
# Si es superusuario y se especifica organizacion en los datos validados
if self.request.user.is_superuser:
# Permitir que el superusuario especifique la organización
if is_internal_service_request(self.request):
serializer.save()
return
if (
self.request.user.groups.filter(name='developer').exists()
or self.request.user.groups.filter(name='admin').exists()
or self.request.user.groups.filter(name='user').exists()
) and self.request.user.groups.filter(name='Agente Aduanal').exists():
# Para usuarios normales, usar siempre su organización
if not hasattr(self.request.user, 'organizacion') or not self.request.user.organizacion:
raise ValueError("Usuario sin organización")
serializer.save(organizacion=self.request.user.organizacion)
return
raise ValueError("Usuario no autenticado o sin permisos para crear Cove")
org = get_org_context(self.request.user)
serializer.save(organizacion=org)
def perform_update(self, serializer):
"""
Permite actualizar un Cove, pero solo si el usuario es superusuario o pertenece a la misma organización.
"""
if not self.request.user.is_authenticated:
raise ValueError("Usuario no autenticado")
# Si es superusuario, permite actualizar sin restricciones
if self.request.user.is_superuser:
if is_internal_service_request(self.request):
serializer.save()
return
if (self.request.user.groups.filter(name='developer').exists() or self.request.user.groups.filter(name='admin').exists() or self.request.user .groups.filter(name='user').exists()) and self.request.user.groups.filter(name='Agente Aduanal').exists():
# Para usuarios normales, usar siempre su organización
if not hasattr(self.request.user, 'organizacion') or not self.request.user.organizacion:
raise ValueError("Usuario sin organización")
serializer.save(organizacion=self.request.user.organizacion)
org = get_org_context(self.request.user)
serializer.save(organizacion=org)
class ImportadorViewSet(viewsets.ModelViewSet, OrganizacionFiltradaMixin):
def perform_destroy(self, instance):
instance.delete()
class ImportadorViewSet(viewsets.ModelViewSet):
"""
ViewSet for Importador model.
"""
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
serializer_class = ImportadorSerializer
pagination_class = CustomPagination
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
@@ -2477,69 +2592,69 @@ class ImportadorViewSet(viewsets.ModelViewSet, OrganizacionFiltradaMixin):
search_fields = ['rfc', 'nombre']
ordering_fields = ['created_at', 'updated_at', 'rfc']
ordering = ['-created_at']
model = Importador
my_tags = ['Importadores']
def get_permissions(self):
# list/retrieve: solo IsAuthenticated — el queryset filtra según permisos
if self.action in ('list', 'retrieve'):
return [IsAuthenticated()]
perms = {
'create': 'importadores.create',
'update': 'importadores.edit',
'partial_update': 'importadores.edit',
'destroy': 'importadores.delete',
}
codename = perms.get(self.action, 'importadores.view')
return [IsAuthenticated(), require_permission(codename)()]
def get_queryset(self):
user = self.request.user
grupos = user.groups.values_list('name', flat=True)
if user.is_superuser:
if is_internal_service_request(self.request):
return Importador.objects.all()
if 'Importador' in grupos:
return user.rfc.all()
return self.get_queryset_filtrado_por_organizacion()
org = get_org_context(user)
if not org:
return Importador.objects.none()
# Con permiso ve todos; sin permiso solo los asignados al usuario
if user_has_permission(user, 'importadores.view'):
return Importador.objects.filter(organizacion=org)
return Importador.objects.filter(organizacion=org, users=user)
def perform_create(self, serializer):
if not self.request.user.is_authenticated or not hasattr(self.request.user, 'organizacion'):
raise ValueError("Usuario no autenticado o sin organización")
serializer.save(organizacion=self.request.user.organizacion)
def perform_update(self, serializer):
if not self.request.user.is_authenticated or not hasattr(self.request.user, 'organizacion'):
raise ValueError("Usuario no autenticado o sin organización")
# Si es superusuario, permite actualizar sin restricciones
if self.request.user.is_superuser:
if is_internal_service_request(self.request):
serializer.save()
return
if (self.request.user.groups.filter(name='developer').exists() or self.request.user.groups.filter(name='admin').exists() or self.request.user.groups.filter(name='user').exists()) and self.request.user.groups.filter(name='Agente Aduanal').exists():
# Para usuarios normales, usar siempre su organización
if not hasattr(self.request.user, 'organizacion') or not self.request.user.organizacion:
raise ValueError("Usuario sin organización")
serializer.save(organizacion=self.request.user.organizacion)
return
raise ValueError("Usuario no autenticado o sin permisos para actualizar Importador")
org = get_org_context(self.request.user)
serializer.save(organizacion=org)
my_tags = ['Importadores']
def perform_update(self, serializer):
if is_internal_service_request(self.request):
serializer.save()
return
org = get_org_context(self.request.user)
serializer.save(organizacion=org)
def perform_destroy(self, instance):
instance.delete()
class EjecutarComandoView(APIView):
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
"""
View para ejecutar el comando de microservicios desde una petición HTTP.
"""
def post(self, request):
permission_classes = [IsAuthenticated, require_permission('pedimentos.process')]
# Obtener organizacion_id del request (si se envía)
organizacion_id_request = request.data.get('organizacionid', None)
def post(self, request):
procesamiento = request.data.get('procesamiento', None)
todos = request.data.get('todos', False)
if not self.request.user.is_authenticated or not hasattr(self.request.user, 'organizacion'):
raise ValueError("Usuario no autenticado o sin organización")
if organizacion_id_request is None:
org = get_org_context(request.user)
if not org:
return Response(
{"error": 'No se proporcionó la organización a ejecutar el proceso.'},
status=status.HTTP_400_BAD_REQUEST
)
{"error": "Sin organización activa."},
status=status.HTTP_403_FORBIDDEN
)
# organizacion_id = self.request.user.organizacion.id
organizacion_id = organizacion_id_request
nombre_organizacion = self.request.user.organizacion.nombre
organizacion_id = str(org.id)
nombre_organizacion = org.nombre
if procesamiento is None and todos == False:
return Response(

View File

@@ -5,7 +5,7 @@ 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 core.permissions import IsSuperUser, IsSameOrganizationDeveloper
from core.permissions import require_permission
from .tasks.auditoria import (
crear_partidas,
auditar_coves,
@@ -84,7 +84,7 @@ def get_document_path(documento):
}
)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.process')])
def crear_partidas_organizacion(request):
organizacion_id = request.data.get('organizacion_id')
@@ -122,7 +122,7 @@ def crear_partidas_organizacion(request):
}
)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
@permission_classes([IsAuthenticated, require_permission('auditoria.process')])
def crear_partidas_pedimento(request):
pedimento_id = request.data.get('pedimento_id')
@@ -202,7 +202,7 @@ def crear_partidas_pedimento(request):
}
)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.process')])
def auditar_pedimentos_endpoint(request):
"""
Inicia una tarea de auditoría para todos los pedimentos de una organización.
@@ -252,7 +252,7 @@ def auditar_pedimentos_endpoint(request):
}
)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auditar_procesamiento_remesa_pedimento_endpoint(request):
pedimento_id = request.data.get('pedimento_id')
@@ -339,7 +339,7 @@ def _lanzar_auditoria_organizacion(request, task_fn, label):
}
)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.process')])
def auditar_coves_endpoint(request):
return _lanzar_auditoria_organizacion(request, auditar_coves, 'COVEs')
@@ -359,7 +359,7 @@ def auditar_coves_endpoint(request):
}
)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.process')])
def auditar_acuse_cove_endpoint(request):
return _lanzar_auditoria_organizacion(request, auditar_acuse_cove, 'acuses de COVE')
@@ -379,7 +379,7 @@ def auditar_acuse_cove_endpoint(request):
}
)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.process')])
def auditar_edocuments_endpoint(request):
return _lanzar_auditoria_organizacion(request, auditar_edocuments, 'EDocuments')
@@ -399,7 +399,7 @@ def auditar_edocuments_endpoint(request):
}
)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.process')])
def auditar_acuse_endpoint(request):
return _lanzar_auditoria_organizacion(request, auditar_acuse, 'acuses de EDocument')
@@ -419,7 +419,7 @@ def auditar_acuse_endpoint(request):
}
)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.process')])
def auditar_remesas_endpoint(request):
return _lanzar_auditoria_organizacion(request, auditar_remesas, 'remesas')
@@ -442,7 +442,7 @@ def auditar_remesas_endpoint(request):
}
)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auditar_cove_pedimento_endpoint(request):
pedimento_id = request.data.get('pedimento_id')
if not pedimento_id:
@@ -504,7 +504,7 @@ def auditar_cove_pedimento_endpoint(request):
}
)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auditar_acuse_cove_pedimento_endpoint(request):
pedimento_id = request.data.get('pedimento_id')
if not pedimento_id:
@@ -566,7 +566,7 @@ def auditar_acuse_cove_pedimento_endpoint(request):
}
)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auditar_edocument_pedimento_endpoint(request):
pedimento_id = request.data.get('pedimento_id')
if not pedimento_id:
@@ -628,7 +628,7 @@ def auditar_edocument_pedimento_endpoint(request):
}
)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auditar_acuse_pedimento_endpoint(request):
pedimento_id = request.data.get('pedimento_id')
if not pedimento_id:
@@ -687,7 +687,7 @@ def auditar_acuse_pedimento_endpoint(request):
}
)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.process')])
def auditor_procesar_pedimentos_organizacion(request):
"""
Inicia una tarea de procesamiento para todos los pedimentos de todas las organizaciones.
@@ -739,7 +739,7 @@ def auditor_procesar_pedimentos_organizacion(request):
### Fin Procesamiento de pedimentos ###
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auditar_peticion_respuesta_pedimento_completo(request):
"""
Backend endpoint para obtener las peticiones y respuestas asociadas a un pedimento.
@@ -884,7 +884,7 @@ def auditar_peticion_respuesta_pedimento_completo(request):
}, status=status.HTTP_200_OK)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auditor_obtener_peticion_pedimento_vu(request):
"""
Backend endpoint para obtener las peticiones y respuestas asociadas a un pedimento.
@@ -938,7 +938,7 @@ def auditor_obtener_peticion_pedimento_vu(request):
}, status=status.HTTP_200_OK)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auditor_obtener_respuesta_pedimento_vu(request):
"""
Backend endpoint para obtener las respuestas asociadas a un pedimento.
@@ -991,7 +991,7 @@ def auditor_obtener_respuesta_pedimento_vu(request):
}, status=status.HTTP_200_OK)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auditor_obtener_peticion_remesa_vu(request):
"""
Backend endpoint para obtener las peticiones asociadas a una remesa.
@@ -1045,7 +1045,7 @@ def auditor_obtener_peticion_remesa_vu(request):
}, status=status.HTTP_200_OK)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auditor_obtener_respuesta_remesa_vu(request):
"""
Backend endpoint para obtener las respuestas asociadas a una remesa.
@@ -1098,7 +1098,7 @@ def auditor_obtener_respuesta_remesa_vu(request):
}, status=status.HTTP_200_OK)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auditor_obtener_peticion_partidas_vu(request):
"""
Backend endpoint para obtener las peticiones asociadas a una remesa.
@@ -1178,7 +1178,7 @@ def auditor_obtener_peticion_partidas_vu(request):
}, status=status.HTTP_200_OK)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auditor_obtener_respuesta_partidas_vu(request):
"""
Backend endpoint para obtener las respuestas asociadas a una remesa.
@@ -1231,7 +1231,7 @@ def auditor_obtener_respuesta_partidas_vu(request):
}, status=status.HTTP_200_OK)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auditor_obtener_peticion_acuse_vu(request):
"""
Backend endpoint para obtener las peticiones asociadas a una remesa.
@@ -1285,7 +1285,7 @@ def auditor_obtener_peticion_acuse_vu(request):
}, status=status.HTTP_200_OK)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auditor_obtener_respuesta_acuse_vu(request):
"""
Backend endpoint para obtener las respuestas asociadas a una remesa.
@@ -1338,7 +1338,7 @@ def auditor_obtener_respuesta_acuse_vu(request):
}, status=status.HTTP_200_OK)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auditor_obtener_peticion_cove_vu(request):
"""
Backend endpoint para obtener las peticiones asociadas a una remesa.
@@ -1392,7 +1392,7 @@ def auditor_obtener_peticion_cove_vu(request):
}, status=status.HTTP_200_OK)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auditor_obtener_respuesta_cove_vu(request):
"""
Backend endpoint para obtener las respuestas asociadas a una remesa.
@@ -1445,7 +1445,7 @@ def auditor_obtener_respuesta_cove_vu(request):
}, status=status.HTTP_200_OK)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auditor_obtener_peticion_acuse_cove_vu(request):
"""
Backend endpoint para obtener las peticiones asociadas a una remesa.
@@ -1499,7 +1499,7 @@ def auditor_obtener_peticion_acuse_cove_vu(request):
}, status=status.HTTP_200_OK)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auditor_obtener_respuesta_acuse_cove_vu(request):
"""
Backend endpoint para obtener las respuestas asociadas a una remesa.
@@ -1552,7 +1552,7 @@ def auditor_obtener_respuesta_acuse_cove_vu(request):
}, status=status.HTTP_200_OK)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auditor_obtener_peticion_edocument_vu(request):
"""
Backend endpoint para obtener las peticiones asociadas a una remesa.
@@ -1606,7 +1606,7 @@ def auditor_obtener_peticion_edocument_vu(request):
}, status=status.HTTP_200_OK)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auditor_obtener_respuesta_edocument_vu(request):
"""
Backend endpoint para obtener las respuestas asociadas a una remesa.
@@ -1677,7 +1677,7 @@ def auditor_obtener_respuesta_edocument_vu(request):
}
)
@api_view(['POST'])
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
@permission_classes([IsAuthenticated, require_permission('auditoria.process')])
def auditar_pedimento_endpoint(request):
"""
Audita un pedimento específico verificando si existe su XML y extrayendo información.