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:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -178,4 +178,4 @@ cython_debug/
|
|||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/django
|
# End of https://www.toptal.com/developers/gitignore/api/django
|
||||||
|
*.bak
|
||||||
|
|||||||
@@ -3,12 +3,17 @@ FROM python:3.11-slim
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Instalar dependencias del sistema necesarias
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends wget && \
|
||||||
|
wget https://www.rarlab.com/rar/rarlinux-x64-621.tar.gz && \
|
||||||
|
tar -xzvf rarlinux*.tar.gz && \
|
||||||
|
cp rar/unrar /usr/bin/unrar && \
|
||||||
|
rm -rf rarlinux*.tar.gz rar
|
||||||
|
|
||||||
COPY requirements.txt ./
|
COPY requirements.txt ./
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
RUN pip install flower
|
RUN pip install flower
|
||||||
|
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,22 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
RAR_SUPPORT = False
|
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):
|
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.
|
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:
|
try:
|
||||||
# rarfile puede trabajar con rutas en disco mejor que con file-like
|
# rarfile puede trabajar con rutas en disco mejor que con file-like
|
||||||
with rarfile.RarFile(rar_path) as rf:
|
with rarfile.RarFile(rar_path) as rf:
|
||||||
rf.extractall(dest_dir)
|
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
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Si rarfile falla (por ejemplo RarCannotExec), seguimos con herramientas externas
|
# 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']
|
['7za', 'x', rar_path, f'-o{dest_dir}', '-y']
|
||||||
]
|
]
|
||||||
|
|
||||||
for cmd in external_cmds:
|
# for cmd in external_cmds:
|
||||||
try:
|
# try:
|
||||||
subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
# subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
return
|
|
||||||
except FileNotFoundError:
|
# try:
|
||||||
# El ejecutable no existe en PATH, intentar siguiente
|
# if os.path.exists(rar_path):
|
||||||
continue
|
# os.remove(rar_path)
|
||||||
except subprocess.CalledProcessError as e:
|
# print(f"Archivo original eliminado: {rar_path}")
|
||||||
# El comando falló en la extracción; intentar siguiente
|
# except OSError as remove_error:
|
||||||
print(f"External extractor failed ({cmd[0]}): {e}")
|
# 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
|
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.")
|
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 *
|
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']
|
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']
|
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):
|
def get_queryset(self):
|
||||||
return self.get_queryset_filtrado_por_organizacion() # Tambien filtra por importador
|
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
|
# Regex para validar nomenclatura: anio-aduana-patente-pedimento
|
||||||
nomenclatura_pattern = re.compile(r'^(\d{2})-(\d{2,3})-(\d{4})-(\d{7})$')
|
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 = []
|
created_pedimentos = []
|
||||||
failed_files = []
|
failed_files = []
|
||||||
@@ -591,7 +659,9 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
|
|||||||
|
|
||||||
# Validar nomenclatura
|
# Validar nomenclatura
|
||||||
match = nomenclatura_pattern.match(folder_name)
|
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}")
|
print(f"Nomenclatura inválida: {folder_name}")
|
||||||
# Determinar el archivo original basado en el subdirectorio
|
# 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')
|
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
|
continue
|
||||||
|
|
||||||
print(f"Nomenclatura válida: {folder_name}")
|
if match:
|
||||||
anio, aduana, patente, pedimento_num = match.groups()
|
|
||||||
print(f"Extraído - Año: {anio}, Aduana: {aduana}, Patente: {patente}, Pedimento: {pedimento_num}")
|
print(f"Nomenclatura válida: {folder_name}")
|
||||||
|
anio, aduana, patente, pedimento_num = match.groups()
|
||||||
# Crear fecha_pago basada en el año
|
print(f"Extraído - Año: {anio}, Aduana: {aduana}, Patente: {patente}, Pedimento: {pedimento_num}")
|
||||||
try:
|
# Formato original: anio-aduana-patente-pedimento
|
||||||
# Convertir año de 2 dígitos a 4 dígitos
|
# Crear fecha_pago basada en el año
|
||||||
anio_completo = 2000 + int(anio) if int(anio) < 50 else 1900 + int(anio)
|
try:
|
||||||
fecha_pago = datetime(anio_completo, 1, 1).date()
|
# Convertir año de 2 dígitos a 4 dígitos
|
||||||
print(f"Fecha de pago calculada: {fecha_pago}")
|
anio_completo = 2000 + int(anio) if int(anio) < 50 else 1900 + int(anio)
|
||||||
except ValueError:
|
fecha_pago = datetime(anio_completo, 1, 1).date()
|
||||||
archivo_original = folder_name + ('.zip' if any(f.endswith('.zip') for f in [a.name for a in archivos]) else '.rar')
|
print(f"Fecha de pago calculada: {fecha_pago}")
|
||||||
failed_files.append({
|
except ValueError:
|
||||||
"file": relative_path,
|
archivo_original = folder_name + ('.zip' if any(f.endswith('.zip') for f in [a.name for a in archivos]) else '.rar')
|
||||||
"archivo_original": archivo_original,
|
failed_files.append({
|
||||||
"error": f"Año inválido: {anio}"
|
"file": relative_path,
|
||||||
})
|
"archivo_original": archivo_original,
|
||||||
continue
|
"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
|
# Generar pedimento_app
|
||||||
pedimento_app = f"{anio}-{aduana.zfill(2)}-{patente}-{pedimento_num}"
|
pedimento_app = f"{anio}-{aduana.zfill(2)}-{patente}-{pedimento_num}"
|
||||||
print(f"Pedimento_app generado: {pedimento_app}")
|
print(f"Pedimento_app generado: {pedimento_app}")
|
||||||
@@ -628,7 +733,7 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
|
|||||||
# Verificar si el pedimento ya existe
|
# Verificar si el pedimento ya existe
|
||||||
existing_pedimento = Pedimento.objects.filter(
|
existing_pedimento = Pedimento.objects.filter(
|
||||||
pedimento_app=pedimento_app,
|
pedimento_app=pedimento_app,
|
||||||
organizacion=organizacion
|
# organizacion=organizacion
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
print(f"Pedimento existente: {existing_pedimento is not None}")
|
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
|
# Crear ContentFile que Django puede manejar correctamente
|
||||||
django_file = ContentFile(file_content, name=file_name)
|
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}")
|
print(f"Creando documento para archivo: {file_name}")
|
||||||
# Crear documento - Django automáticamente guardará el archivo en media/documents/
|
# Crear documento - Django automáticamente guardará el archivo en media/documents/
|
||||||
document = Document.objects.create(
|
document = Document.objects.create(
|
||||||
organizacion=organizacion,
|
organizacion=organizacion,
|
||||||
pedimento_id=pedimento.id,
|
pedimento_id=pedimento.id,
|
||||||
document_type=document_type,
|
document_type=document_type,
|
||||||
|
fuente_id=4, # Fuente: Carga Plataforma
|
||||||
archivo=django_file,
|
archivo=django_file,
|
||||||
size=len(file_content),
|
size=len(file_content),
|
||||||
extension=os.path.splitext(file_name)[1].lower().lstrip('.')
|
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')
|
modulo_efc = self.request.query_params.get('modulo')
|
||||||
if modulo_efc:
|
if modulo_efc:
|
||||||
if modulo_efc == 'expedientes-detalle-pedimentos':
|
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
|
# Filtro personalizado por document_type
|
||||||
# document_type = self.request.query_params.get('document_type')
|
# document_type = self.request.query_params.get('document_type')
|
||||||
# if document_type:
|
# if document_type:
|
||||||
|
|||||||
Reference in New Issue
Block a user