6 Commits

9 changed files with 213 additions and 16 deletions

View File

@@ -36,7 +36,8 @@ class Command(BaseCommand):
if organizacion_id:
if procesamiento:
microservice_v2.ejecutar_procesamiento_por_organizacion(organizacion_id, procesamiento)
# microservice_v2.ejecutar_procesamiento_por_organizacion(organizacion_id, procesamiento)
microservice_v2.ejecutar_por_organizacion_y_procesamiento(organizacion_id, procesamiento)
self.stdout.write(self.style.SUCCESS(f'Se ejecutó el procesamiento {procesamiento} para la organización {organizacion_id}.'))
else:
microservice_v2.ejecutar_todos_por_organizacion(organizacion_id)

View File

@@ -61,7 +61,7 @@ class Pedimento(models.Model):
db_table = 'pedimento'
ordering = ['pedimento']
unique_together = [
['organizacion', 'pedimento'],
# ['organizacion', 'pedimento'],
['organizacion', 'pedimento_app']
]

View File

@@ -222,14 +222,15 @@ def procesar_pedimentos_completos(organizacion_id):
pedimento_dict = pedimento_to_dict(pedimento)
credenciales = Vucem.objects.filter(id=CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first().vucem.id).first()
credenciales_dict = credenciales_to_dict(credenciales)
if not credenciales:
print(f"No se encontraron credenciales para el pedimento {pedimento.pedimento_app}")
continue
credenciales_dict = credenciales_to_dict(credenciales)
payload = {
"pedimento": pedimento_dict,
"credencial": credenciales_dict
}
response = requests.post(
f"{SERVICE_API_URL_V2}/services/pedimento_completo",
data=json.dumps(payload),
@@ -428,6 +429,34 @@ def documentos_con_errores(organizacion_id):
# Aquí puedes agregar lógica adicional para manejar documentos con errores
# como enviar notificaciones, registrar en un log, etc.
@shared_task
def procesar_procesamiento_pedimento(organizacion_id):
# print("Creando procesamientos de pedimentos para organización:", organizacion_id)
pedimentos = Pedimento.objects.filter(organizacion_id=organizacion_id)
# pedimentos = Pedimento.objects.filter(id='1c061182-ac68-45b0-b3d7-35bf2264982b')
if not pedimentos.exists():
print("No se encontraron pedimentos para la organización:", organizacion_id)
return
for pedimento in pedimentos:
if not pedimento.documents.filter(document_type=2).exists(): # Tipo 2: Pedimento Completo
procesamiento_pedimento = ProcesamientoPedimento.objects.filter(
pedimento_id=pedimento.id,
servicio_id=3, # servicio 3: Pedimento Completo
)
if not procesamiento_pedimento.exists():
ProcesamientoPedimento.objects.create(
pedimento_id=pedimento.id
, organizacion_id=pedimento.organizacion_id
, estado_id =1
, servicio_id=3
, tipo_procesamiento_id=2) # servicio 3: Pedimento Completo
# print("Procesamiento creado para pedimento:", pedimento.pedimento_app)
procesar_pedimentos_completos.delay(organizacion_id)
def ejecutar_por_organizacion_y_procesamiento(organizacion_id, procesamiento):
if procesamiento == 'coves':
@@ -444,9 +473,11 @@ def ejecutar_por_organizacion_y_procesamiento(organizacion_id, procesamiento):
procesar_pedimentos_completos.delay(organizacion_id)
elif procesamiento == 'remesas':
procesar_remesas.delay(organizacion_id)
elif procesamiento == 'procesamiento_pedimento':
procesar_procesamiento_pedimento.delay(organizacion_id)
else:
# Procesamiento no reconocido
# print(f"Procesamiento no reconocido: {procesamiento}")
pass
def ejecutar_todos_por_organizacion(organizacion_id):
@@ -459,3 +490,5 @@ def ejecutar_todos_por_organizacion(organizacion_id):
procesar_remesas.delay(organizacion_id)

View File

@@ -10,7 +10,8 @@ from .views import (
ViewSetEDocument,
ViewSetCove,
ImportadorViewSet,
PartidaViewSet
PartidaViewSet,
EjecutarComandoView
)
# from .views import YourViewSet # Import your viewsets here
@@ -95,4 +96,7 @@ urlpatterns = [
path('auditor/obtener-respuesta/acuse-cove-vu/', auditor_obtener_respuesta_acuse_cove_vu, name='obtener-respuesta-acuse-cove-vu'),
path('auditor/obtener-peticion/edocument-vu/', auditor_obtener_peticion_edocument_vu, name='obtener-peticion-edocument-vu'),
path('auditor/obtener-respuesta/edocument-vu/', auditor_obtener_respuesta_edocument_vu, name='obtener-respuesta-edocument-vu'),
path('procesamientopedimentos-ejecutar-comando/', EjecutarComandoView.as_view(), name='procesamientopedimentos-ejecutar-comando'),
]

View File

@@ -1777,6 +1777,57 @@ class ImportadorViewSet(viewsets.ModelViewSet, OrganizacionFiltradaMixin):
my_tags = ['Importadores']
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):
# organizacion_id = request.data.get('organizacion_id', None)
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")
organizacion_id = self.request.user.organizacion.id
nombre_organizacion = self.request.user.organizacion.nombre
if procesamiento is None and todos == False:
return Response(
{"message": 'No se detectó el tipo de ejecución de procesamiento.'},
status=status.HTTP_400_BAD_REQUEST
)
procesamiento = str(procesamiento)
from api.customs.tasks import microservice_v2
if todos:
microservice_v2.ejecutar_todos_por_organizacion(organizacion_id)
return Response(
{"message": f'Se estarán ejecutando todos los procesos para la organización {nombre_organizacion} en segundo plano.'},
status=status.HTTP_200_OK
)
elif organizacion_id:
if procesamiento:
microservice_v2.ejecutar_por_organizacion_y_procesamiento(organizacion_id, procesamiento)
return Response(
{"message": f'Se estará ejecutando el procesamiento {procesamiento} para la organización {nombre_organizacion} en segundo plano.'},
status=status.HTTP_200_OK
)
return Response(
{"error": "Parámetros insuficientes. Proporcione 'organizacion' y 'procesamiento', o seleccione 'todos'."},
status=status.HTTP_400_BAD_REQUEST
)
my_tags = ['Procesamientos_Pedimentos']
# helper | reglas para formato de docuemnto antes de cargarlo
def normalize_filename(filename):
"""

View File

@@ -667,28 +667,36 @@ def auditar_peticion_respuesta_pedimento_completo(request):
pedimento_app = pedimento.pedimento_app
tipo_documento_peticion = None
tipo_documento_respuesta = None
vista = 'desconocido'
if vista_auditar == 'pc':
tipo_documento_peticion = 13
tipo_documento_respuesta = 14
vista = 'Pedimento Completo'
elif vista_auditar == 'rm':
tipo_documento_peticion = 15
tipo_documento_respuesta = 16
vista = 'Remesa'
elif vista_auditar == 'pt':
tipo_documento_peticion = 17
tipo_documento_respuesta = 18
vista = 'Partidas'
elif vista_auditar == 'cove':
tipo_documento_peticion = 19
tipo_documento_respuesta = 20
vista = 'COVEs'
elif vista_auditar == 'edoc':
tipo_documento_peticion = 21
tipo_documento_respuesta = 22
vista = 'Edocuments'
elif vista_auditar == 'ac_cove':
tipo_documento_peticion = 23
tipo_documento_respuesta = 24
vista = 'Acuses COVEs'
elif vista_auditar == 'ac':
tipo_documento_peticion = 25
tipo_documento_respuesta = 26
vista = 'Acuses'
if not tipo_documento_peticion and not tipo_documento_respuesta:
return Response(
@@ -712,7 +720,7 @@ def auditar_peticion_respuesta_pedimento_completo(request):
if not documentos_peticion and not documentos_respuesta:
return Response(
{'error': 'Registro de documentos de petición y respuesta de partidas no encontrado'},
{'error': f'Registro de documentos de petición y respuesta de {vista} no encontrado(s)'},
status=status.HTTP_404_NOT_FOUND
)

View File

@@ -15,6 +15,7 @@ class Document(models.Model):
extension = models.CharField(max_length=60, blank=True, null=True)
size = models.PositiveIntegerField()
fuente = models.ForeignKey('Fuente', on_delete=models.CASCADE, related_name='documents', blank=True, null=True)
vu = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
@@ -22,6 +23,13 @@ class Document(models.Model):
def save(self, *args, **kwargs):
is_new = self._state.adding
# Calcular automáticamente el campo vu
if self.document_type_id:
# rango de IDs que indican documentos VU
self.vu = 13 <= self.document_type_id <= 26
else:
self.vu = False
# Usar get_or_create en lugar de get para manejar el caso cuando no existe
uso_almacenamiento, created = UsoAlmacenamiento.objects.get_or_create(
organizacion=self.organizacion,

View File

@@ -13,7 +13,7 @@ class DocumentSerializer(serializers.ModelSerializer):
fuente = serializers.PrimaryKeyRelatedField(queryset=Fuente.objects.all())
class Meta:
model = Document
fields = ('id', 'organizacion', 'pedimento', 'pedimento_numero', 'archivo', 'document_type', 'size', 'extension', 'fuente','fuente_nombre','created_at', 'updated_at')
fields = ('id', 'organizacion', 'pedimento', 'pedimento_numero', 'archivo', 'document_type', 'size', 'extension', 'fuente','fuente_nombre','created_at', 'updated_at','vu')
read_only_fields = ('id', 'size', 'extension', 'created_at', 'updated_at', 'pedimento_numero')
def get_pedimento_numero(self, obj):

View File

@@ -313,6 +313,85 @@ class DocumentViewSet(viewsets.ModelViewSet, DocumentosFiltradosMixin):
uso.save()
instance.delete()
@action(detail=False, methods=['get'], url_path='vu-documentos-errores')
def vu_documentos_errores(self, request):
"""
Endpoint para obtener los documentos VU de error obtenidoss.
Filtra documentos cuyo document_type está en el rango de IDs de documentos VU (13-26).
"""
queryset = self.get_queryset().filter(vu=True)
pedimento_id = request.query_params.get('pedimentoId')
filtroExtension = request.query_params.get('extension')
filtroArchivo = request.query_params.get('archivo__icontains')
filtroFechaCreacion = request.query_params.get('created_at__date')
filtroTipoError = request.query_params.get('tipo_error')
filtroFuente = request.query_params.get('fuente')
document_type_ids = request.query_params.get('document_type_id')
if pedimento_id:
try:
pedimento_obj = Pedimento.objects.get(id=pedimento_id)
queryset = queryset.filter(pedimento_id=pedimento_id)
except Pedimento.DoesNotExist:
return Response(
{"error": "No se encontró el pedimento especificado"},
status=status.HTTP_404_NOT_FOUND
)
if filtroArchivo:
try:
queryset = queryset.filter(archivo__icontains=filtroArchivo)
except ValueError:
return Response(
{"error": "El parámetro Archivo debe ser caracteres válidos"},
status=status.HTTP_400_BAD_REQUEST
)
if filtroExtension:
try:
queryset = queryset.filter(extension__iexact=filtroExtension)
except ValueError:
return Response(
{"error": "El parámetro extension debe ser una extensión válida"},
status=status.HTTP_400_BAD_REQUEST
)
if filtroFechaCreacion:
from django.utils.dateparse import parse_date
fecha = parse_date(filtroFechaCreacion)
if not fecha:
return Response(
{"error": "El parámetro created_at__date debe tener el formato YYYY-MM-DD"},
status=status.HTTP_400_BAD_REQUEST
)
queryset = queryset.filter(created_at__date=fecha)
if filtroTipoError:
try:
ids = [int(i) for i in filtroTipoError.split(',')]
queryset = queryset.filter(document_type_id__in=ids)
except ValueError:
return Response(
{"error": "El parámetro document_type_id debe ser una lista de IDs separados por comas"},
status=status.HTTP_400_BAD_REQUEST
)
if filtroFuente:
try:
ids = [int(i) for i in filtroFuente.split(',')]
queryset = queryset.filter(fuente_id__in=ids)
except ValueError:
return Response(
{"error": "El parámetro fuente debe ser una lista de IDs separados por comas"},
status=status.HTTP_400_BAD_REQUEST
)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@action(detail=False, methods=['post'], url_path='bulk-delete')
def bulk_delete(self, request):
"""
@@ -425,9 +504,22 @@ class DocumentViewSet(viewsets.ModelViewSet, DocumentosFiltradosMixin):
# Si no existe el registro, no hay nada que actualizar
pass
# Eliminar los documentos
deleted_count = existing_documents.count()
existing_documents.delete()
# Eliminar los documentos (archivos físicos y registros de BD)
archivos_eliminados = 0
for doc in existing_documents:
try:
# Eliminar archivo físico
if doc.archivo and doc.archivo.storage.exists(doc.archivo.name):
doc.archivo.delete(save=False) # save=False para no intentar guardar el modelo
# Eliminar registro de la base de datos
doc.delete()
archivos_eliminados += 1
except Exception as e:
errors.append(f"No se pudo eliminar el documento {doc.id}: {str(e)}")
failed_ids.append(str(doc.id))
deleted_count = archivos_eliminados
except Exception as e:
return Response(
@@ -437,7 +529,7 @@ class DocumentViewSet(viewsets.ModelViewSet, DocumentosFiltradosMixin):
# Agregar errores para IDs no encontrados
if failed_ids:
errors = [f"No se encontró el documento con ID {id} o no pertenece a su organización" for id in failed_ids]
errors.extend([f"No se encontró el documento con ID {id} o no pertenece a su organización" for id in failed_ids])
# Convertir bytes a MB para la respuesta
space_freed_mb = round(total_space_freed / (1024 * 1024), 2)
@@ -449,7 +541,7 @@ class DocumentViewSet(viewsets.ModelViewSet, DocumentosFiltradosMixin):
"space_freed_mb": space_freed_mb
}
if failed_ids:
if errors or failed_ids:
response_data.update({
"message": "Algunos documentos no pudieron ser eliminados",
"failed_ids": failed_ids,