fix: Se ajusta carga de expediente en RAR y se agega validacion para evitar duplicar el pedimento independientemente de la organizacion de carga.
This commit is contained in:
@@ -57,6 +57,22 @@ try:
|
||||
except ImportError:
|
||||
RAR_SUPPORT = False
|
||||
|
||||
def get_available_extractors():
|
||||
"""
|
||||
Devuelve lista de extractores disponibles en orden de preferencia
|
||||
"""
|
||||
extractors = []
|
||||
if RAR_SUPPORT:
|
||||
extractors.append('rarfile')
|
||||
# Verificar si 'unrar' está disponible
|
||||
if shutil.which('unrar'):
|
||||
extractors.append('unrar')
|
||||
# Verificar si '7z' o '7za' están disponibles
|
||||
if shutil.which('7z'):
|
||||
extractors.append('7z')
|
||||
elif shutil.which('7za'):
|
||||
extractors.append('7za')
|
||||
return extractors
|
||||
|
||||
def extract_rar_to_dir(rar_path, dest_dir):
|
||||
"""
|
||||
@@ -67,12 +83,29 @@ def extract_rar_to_dir(rar_path, dest_dir):
|
||||
|
||||
Lanza Exception con mensaje explicativo si falla.
|
||||
"""
|
||||
# Intento con rarfile (Python)
|
||||
if RAR_SUPPORT:
|
||||
|
||||
# Versión que primero verifica herramientas disponibles
|
||||
available = get_available_extractors()
|
||||
if not available:
|
||||
raise Exception("No hay herramientas de extracción disponibles.")
|
||||
|
||||
print(f"Extractores disponibles (en orden de preferencia): {available}")
|
||||
|
||||
# Intento con rarfile primero si está disponible
|
||||
# if RAR_SUPPORT:
|
||||
if 'rarfile' in available and RAR_SUPPORT:
|
||||
try:
|
||||
# rarfile puede trabajar con rutas en disco mejor que con file-like
|
||||
with rarfile.RarFile(rar_path) as rf:
|
||||
rf.extractall(dest_dir)
|
||||
|
||||
try:
|
||||
if os.path.exists(rar_path):
|
||||
os.remove(rar_path)
|
||||
print(f"Archivo original eliminado: {rar_path}")
|
||||
except OSError as remove_error:
|
||||
print(f"Advertencia: No se pudo eliminar '{rar_path}': {remove_error}")
|
||||
|
||||
return
|
||||
except Exception as e:
|
||||
# Si rarfile falla (por ejemplo RarCannotExec), seguimos con herramientas externas
|
||||
@@ -87,18 +120,49 @@ def extract_rar_to_dir(rar_path, dest_dir):
|
||||
['7za', 'x', rar_path, f'-o{dest_dir}', '-y']
|
||||
]
|
||||
|
||||
for cmd in external_cmds:
|
||||
try:
|
||||
subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
return
|
||||
except FileNotFoundError:
|
||||
# El ejecutable no existe en PATH, intentar siguiente
|
||||
continue
|
||||
except subprocess.CalledProcessError as e:
|
||||
# El comando falló en la extracción; intentar siguiente
|
||||
print(f"External extractor failed ({cmd[0]}): {e}")
|
||||
# for cmd in external_cmds:
|
||||
# try:
|
||||
# subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
# try:
|
||||
# if os.path.exists(rar_path):
|
||||
# os.remove(rar_path)
|
||||
# print(f"Archivo original eliminado: {rar_path}")
|
||||
# except OSError as remove_error:
|
||||
# print(f"Advertencia: No se pudo eliminar '{rar_path}': {remove_error}")
|
||||
|
||||
# return
|
||||
# except FileNotFoundError:
|
||||
# # El ejecutable no existe en PATH, intentar siguiente
|
||||
# continue
|
||||
# except subprocess.CalledProcessError as e:
|
||||
# # El comando falló en la extracción; intentar siguiente
|
||||
# print(f"External extractor failed ({cmd[0]}): {e}")
|
||||
# continue
|
||||
|
||||
for extractor_name in available:
|
||||
if extractor_name in external_cmds:
|
||||
cmd = external_cmds[extractor_name]
|
||||
try:
|
||||
subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
try:
|
||||
if os.path.exists(rar_path):
|
||||
os.remove(rar_path)
|
||||
print(f"Archivo original eliminado: {rar_path}")
|
||||
except OSError as remove_error:
|
||||
print(f"Advertencia: No se pudo eliminar '{rar_path}': {remove_error}")
|
||||
|
||||
return
|
||||
except FileNotFoundError:
|
||||
# El ejecutable no existe en PATH, intentar siguiente
|
||||
continue
|
||||
except subprocess.CalledProcessError as e:
|
||||
# El comando falló en la extracción; intentar siguiente
|
||||
print(f"External extractor {extractor_name} failed ({cmd[0]}): {e}")
|
||||
continue
|
||||
|
||||
# Si llegamos aquí, ningún método funcionó
|
||||
raise Exception("No se encontró una herramienta válida para extraer RAR (rarfile sin backend, 'unrar' o '7z' no disponibles o extracción fallida). Instale 'unrar' o 'p7zip' y asegúrese de que estén en PATH, o configure rarfile con un backend.")
|
||||
|
||||
from .tasks.microservice_v2 import *
|
||||
@@ -212,7 +276,10 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
|
||||
|
||||
filterset_fields = ['patente', 'aduana', 'tipo_operacion', 'clave_pedimento', 'pedimento', 'existe_expediente', 'contribuyente', 'curp_apoderado', 'fecha_pago', 'pedimento_app']
|
||||
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
|
||||
|
||||
def get_queryset(self):
|
||||
return self.get_queryset_filtrado_por_organizacion() # Tambien filtra por importador
|
||||
|
||||
@@ -444,6 +511,7 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
|
||||
|
||||
# Regex para validar nomenclatura: anio-aduana-patente-pedimento
|
||||
nomenclatura_pattern = re.compile(r'^(\d{2})-(\d{2,3})-(\d{4})-(\d{7})$')
|
||||
nomenclatura_pattern_sin_anio = re.compile(r'^(\d{2,3})-(\d{4})-(\d{7})$')
|
||||
|
||||
created_pedimentos = []
|
||||
failed_files = []
|
||||
@@ -591,7 +659,9 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
|
||||
|
||||
# Validar nomenclatura
|
||||
match = nomenclatura_pattern.match(folder_name)
|
||||
if not match:
|
||||
match_sin_anio = nomenclatura_pattern_sin_anio.match(folder_name)
|
||||
|
||||
if not match and not match_sin_anio:
|
||||
print(f"Nomenclatura inválida: {folder_name}")
|
||||
# Determinar el archivo original basado en el subdirectorio
|
||||
archivo_original = folder_name + ('.zip' if any(f.endswith('.zip') for f in [a.name for a in archivos]) else '.rar')
|
||||
@@ -602,25 +672,60 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
|
||||
})
|
||||
continue
|
||||
|
||||
print(f"Nomenclatura válida: {folder_name}")
|
||||
anio, aduana, patente, pedimento_num = match.groups()
|
||||
print(f"Extraído - Año: {anio}, Aduana: {aduana}, Patente: {patente}, Pedimento: {pedimento_num}")
|
||||
|
||||
# Crear fecha_pago basada en el año
|
||||
try:
|
||||
# Convertir año de 2 dígitos a 4 dígitos
|
||||
anio_completo = 2000 + int(anio) if int(anio) < 50 else 1900 + int(anio)
|
||||
fecha_pago = datetime(anio_completo, 1, 1).date()
|
||||
print(f"Fecha de pago calculada: {fecha_pago}")
|
||||
except ValueError:
|
||||
archivo_original = folder_name + ('.zip' if any(f.endswith('.zip') for f in [a.name for a in archivos]) else '.rar')
|
||||
failed_files.append({
|
||||
"file": relative_path,
|
||||
"archivo_original": archivo_original,
|
||||
"error": f"Año inválido: {anio}"
|
||||
})
|
||||
continue
|
||||
if match:
|
||||
|
||||
print(f"Nomenclatura válida: {folder_name}")
|
||||
anio, aduana, patente, pedimento_num = match.groups()
|
||||
print(f"Extraído - Año: {anio}, Aduana: {aduana}, Patente: {patente}, Pedimento: {pedimento_num}")
|
||||
# Formato original: anio-aduana-patente-pedimento
|
||||
# Crear fecha_pago basada en el año
|
||||
try:
|
||||
# Convertir año de 2 dígitos a 4 dígitos
|
||||
anio_completo = 2000 + int(anio) if int(anio) < 50 else 1900 + int(anio)
|
||||
fecha_pago = datetime(anio_completo, 1, 1).date()
|
||||
print(f"Fecha de pago calculada: {fecha_pago}")
|
||||
except ValueError:
|
||||
archivo_original = folder_name + ('.zip' if any(f.endswith('.zip') for f in [a.name for a in archivos]) else '.rar')
|
||||
failed_files.append({
|
||||
"file": relative_path,
|
||||
"archivo_original": archivo_original,
|
||||
"error": f"Año inválido: {anio}"
|
||||
})
|
||||
continue
|
||||
|
||||
elif match_sin_anio:
|
||||
|
||||
print(f"Nomenclatura válida sin año: {folder_name}")
|
||||
|
||||
# Formato sin año: aduana-patente-pedimento
|
||||
aduana, patente, pedimento_num = match_sin_anio.groups()
|
||||
print(f"Extraído - Aduana: {aduana}, Patente: {patente}, Pedimento: {pedimento_num}")
|
||||
|
||||
# Obtener el primer dígito del pedimento
|
||||
primer_digito_pedimento = int(pedimento_num[0]) if pedimento_num else 0
|
||||
|
||||
# Usar año actual para fecha_pago y ajustar según el dígito del pedimento
|
||||
año_actual = datetime.now().year
|
||||
|
||||
# Crear año con el dígito del pedimento (reemplazando el último dígito)
|
||||
año_con_digito = int(str(año_actual)[:-1] + str(primer_digito_pedimento))
|
||||
|
||||
# Aplicar lógica de comparación
|
||||
if año_con_digito <= año_actual:
|
||||
# Si el año con dígito es menor o igual al año actual
|
||||
año_final = año_con_digito
|
||||
else:
|
||||
# Si el año con dígito es mayor al año actual, restar 10
|
||||
año_final = año_con_digito - 10
|
||||
|
||||
# Tomar los últimos 2 dígitos del año final
|
||||
anio = año_final % 100
|
||||
|
||||
# Crear fecha de pago (primer día del año)
|
||||
fecha_pago = datetime(año_final , 1, 1).date()
|
||||
|
||||
print(f"Fecha de pago (año actual) calculada: {fecha_pago}")
|
||||
|
||||
# Generar pedimento_app
|
||||
pedimento_app = f"{anio}-{aduana.zfill(2)}-{patente}-{pedimento_num}"
|
||||
print(f"Pedimento_app generado: {pedimento_app}")
|
||||
@@ -628,7 +733,7 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
|
||||
# Verificar si el pedimento ya existe
|
||||
existing_pedimento = Pedimento.objects.filter(
|
||||
pedimento_app=pedimento_app,
|
||||
organizacion=organizacion
|
||||
# organizacion=organizacion
|
||||
).first()
|
||||
|
||||
print(f"Pedimento existente: {existing_pedimento is not None}")
|
||||
@@ -700,12 +805,32 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
|
||||
# Crear ContentFile que Django puede manejar correctamente
|
||||
django_file = ContentFile(file_content, name=file_name)
|
||||
|
||||
# # Verificar si el documento ya existe para este pedimento y archivo
|
||||
# print("🔍 Verificando existencia previa del documento...")
|
||||
|
||||
# # Reemplazar múltiples caracteres
|
||||
# normalized_file_name = file_name.replace(" ", "_")
|
||||
|
||||
# file_name_without_extension = normalized_file_name.rsplit('.', 1)[0]
|
||||
# extension_file = os.path.splitext(normalized_file_name)[1].lower().lstrip('.')
|
||||
|
||||
# existing_document = Document.objects.filter(
|
||||
# pedimento_id=pedimento.id,
|
||||
# archivo__contains=file_name_without_extension,
|
||||
# extension=extension_file
|
||||
# ).first()
|
||||
|
||||
# if existing_document:
|
||||
# print(f"Documento existente encontrado, omitiendo creación: ID {existing_document.id}")
|
||||
# continue
|
||||
|
||||
print(f"Creando documento para archivo: {file_name}")
|
||||
# Crear documento - Django automáticamente guardará el archivo en media/documents/
|
||||
document = Document.objects.create(
|
||||
organizacion=organizacion,
|
||||
pedimento_id=pedimento.id,
|
||||
document_type=document_type,
|
||||
fuente_id=4, # Fuente: Carga Plataforma
|
||||
archivo=django_file,
|
||||
size=len(file_content),
|
||||
extension=os.path.splitext(file_name)[1].lower().lstrip('.')
|
||||
|
||||
@@ -76,7 +76,7 @@ class DocumentViewSet(viewsets.ModelViewSet, DocumentosFiltradosMixin):
|
||||
modulo_efc = self.request.query_params.get('modulo')
|
||||
if modulo_efc:
|
||||
if modulo_efc == 'expedientes-detalle-pedimentos':
|
||||
queryset = queryset.filter(document_type_id='11')
|
||||
queryset = queryset.exclude(document_type_id__in=['1','2','3','4','5','6','7','8','9','10'])
|
||||
# Filtro personalizado por document_type
|
||||
# document_type = self.request.query_params.get('document_type')
|
||||
# if document_type:
|
||||
|
||||
Reference in New Issue
Block a user