import os import json import zipfile import tempfile from datetime import datetime import pytz from flask import current_app from services.project_service import get_project, find_project_directory from services.document_service import get_project_documents, get_latest_version from utils.file_utils import ensure_dir_exists def export_project(project_id, document_ids=None, include_metadata=True): """ Exportar un proyecto completo o documentos seleccionados a un archivo ZIP. Args: project_id (int): ID del proyecto a exportar document_ids (list, optional): Lista de IDs de documentos a incluir. Si es None, se incluyen todos los documentos. include_metadata (bool, optional): Incluir metadatos del proyecto y documentos. Por defecto es True. Returns: tuple: (success, message, zip_path) - success (bool): True si la exportación fue exitosa - message (str): Mensaje descriptivo - zip_path (str): Ruta al archivo ZIP generado """ # Verificar que el proyecto existe project = get_project(project_id) if not project: return False, f"No se encontró el proyecto con ID {project_id}.", None # Obtener directorio del proyecto project_dir = find_project_directory(project_id) if not project_dir: return False, f"No se encontró el directorio del proyecto con ID {project_id}.", None # Crear directorio temporal para la exportación temp_dir = tempfile.mkdtemp() try: # Obtener documentos del proyecto all_documents = get_project_documents(project_id) # Filtrar documentos si se especificaron IDs if document_ids: documents = [doc for doc in all_documents if doc.get('id') in document_ids] else: documents = all_documents if not documents: return False, "No hay documentos para exportar.", None # Crear nombre para el archivo ZIP timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') project_code = project.get('codigo', f'PROJ{project_id}') zip_filename = f"{project_code}_export_{timestamp}.zip" # Ruta completa al archivo ZIP storage_path = current_app.config['STORAGE_PATH'] exports_dir = os.path.join(storage_path, 'exports') ensure_dir_exists(exports_dir) zip_path = os.path.join(exports_dir, zip_filename) # Crear archivo ZIP with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf: # Incluir metadatos del proyecto si se solicita if include_metadata: # Crear archivo JSON con metadatos del proyecto project_meta = { 'codigo': project.get('codigo'), 'descripcion': project.get('descripcion'), 'cliente': project.get('cliente'), 'destinacion': project.get('destinacion'), 'ano_creacion': project.get('ano_creacion'), 'fecha_creacion': project.get('fecha_creacion'), 'creado_por': project.get('creado_por'), 'fecha_exportacion': datetime.now(pytz.UTC).isoformat(), 'documentos_incluidos': len(documents) } # Guardar metadatos en archivo temporal meta_file = os.path.join(temp_dir, 'project_info.json') with open(meta_file, 'w', encoding='utf-8') as f: json.dump(project_meta, f, ensure_ascii=False, indent=2) # Añadir al ZIP zipf.write(meta_file, arcname='project_info.json') # Añadir documentos (última versión de cada uno) for document in documents: doc_id = document.get('id') doc_name = document.get('document_id', '').split('_', 1)[1] if '_' in document.get('document_id', '') else f'doc_{doc_id}' # Obtener última versión version_meta, file_path = get_latest_version(project_id, doc_id) if not version_meta or not file_path or not os.path.exists(file_path): continue # Nombre para el archivo en el ZIP version_num = version_meta.get('version', 1) original_filename = document.get('original_filename', os.path.basename(file_path)) # Usar nombre original o generar uno basado en metadatos if '.' in original_filename: base_name, extension = original_filename.rsplit('.', 1) zip_filename = f"{doc_name}_v{version_num}.{extension}" else: zip_filename = f"{doc_name}_v{version_num}" # Añadir archivo al ZIP zipf.write(file_path, arcname=f'documents/{zip_filename}') # Incluir metadatos del documento si se solicita if include_metadata: # Crear metadatos simplificados doc_meta = { 'nombre': doc_name, 'version': version_num, 'fecha_creacion': version_meta.get('created_at'), 'creado_por': version_meta.get('created_by'), 'descripcion': version_meta.get('description', ''), 'tamano': version_meta.get('file_size', 0) } # Guardar metadatos en archivo temporal doc_meta_file = os.path.join(temp_dir, f'{doc_name}_meta.json') with open(doc_meta_file, 'w', encoding='utf-8') as f: json.dump(doc_meta, f, ensure_ascii=False, indent=2) # Añadir al ZIP zipf.write(doc_meta_file, arcname=f'documents/{doc_name}_meta.json') return True, f"Proyecto exportado correctamente. {len(documents)} documentos incluidos.", zip_path except Exception as e: return False, f"Error al exportar proyecto: {str(e)}", None finally: # Limpiar directorio temporal import shutil shutil.rmtree(temp_dir) def export_document_versions(project_id, document_id, include_all_versions=False): """ Exportar todas las versiones de un documento específico. Args: project_id (int): ID del proyecto document_id (int): ID del documento include_all_versions (bool, optional): Incluir todas las versiones. Si es False, solo se incluye la última versión. Returns: tuple: (success, message, zip_path) """ from services.document_service import get_document, find_document_directory # Verificar que el proyecto existe project = get_project(project_id) if not project: return False, f"No se encontró el proyecto con ID {project_id}.", None # Obtener directorio del proyecto project_dir = find_project_directory(project_id) if not project_dir: return False, f"No se encontró el directorio del proyecto con ID {project_id}.", None # Obtener documento document = get_document(project_id, document_id) if not document: return False, f"No se encontró el documento con ID {document_id}.", None # Obtener directorio del documento document_dir = find_document_directory(project_dir, document_id) if not document_dir: return False, f"No se encontró el directorio del documento con ID {document_id}.", None # Crear nombre para el archivo ZIP timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') doc_name = document.get('document_id', '').split('_', 1)[1] if '_' in document.get('document_id', '') else f'doc_{document_id}' zip_filename = f"{doc_name}_export_{timestamp}.zip" # Ruta completa al archivo ZIP storage_path = current_app.config['STORAGE_PATH'] exports_dir = os.path.join(storage_path, 'exports') ensure_dir_exists(exports_dir) zip_path = os.path.join(exports_dir, zip_filename) try: # Crear archivo ZIP with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf: # Obtener versiones versions = document.get('versions', []) if not versions: return False, "El documento no tiene versiones.", None # Si solo se quiere la última versión if not include_all_versions: latest_version = max(versions, key=lambda v: v['version']) versions = [latest_version] # Añadir cada versión al ZIP for version in versions: version_num = version.get('version', 1) version_filename = version.get('filename') if not version_filename: continue file_path = os.path.join(document_dir, version_filename) if not os.path.exists(file_path): continue # Añadir archivo al ZIP zipf.write(file_path, arcname=f'v{version_num}_{version_filename}') # Añadir metadatos meta_data = { 'document_id': document.get('document_id'), 'original_filename': document.get('original_filename'), 'versions_included': len(versions), 'export_date': datetime.now(pytz.UTC).isoformat() } # Crear archivo temporal para metadatos with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.json') as temp: json.dump(meta_data, temp, ensure_ascii=False, indent=2) temp_path = temp.name # Añadir metadatos al ZIP zipf.write(temp_path, arcname='document_info.json') # Eliminar archivo temporal os.unlink(temp_path) return True, f"Documento exportado correctamente. {len(versions)} versiones incluidas.", zip_path except Exception as e: return False, f"Error al exportar documento: {str(e)}", None def create_project_report(project_id, report_type='summary'): """ Crear un informe del proyecto en formato JSON. Args: project_id (int): ID del proyecto report_type (str, optional): Tipo de informe ('summary', 'detailed'). Por defecto es 'summary'. Returns: tuple: (success, message, report_data) """ # Verificar que el proyecto existe project = get_project(project_id) if not project: return False, f"No se encontró el proyecto con ID {project_id}.", None # Obtener documentos del proyecto documents = get_project_documents(project_id) # Crear informe básico report = { 'project_code': project.get('codigo'), 'description': project.get('descripcion'), 'client': project.get('cliente'), 'destination': project.get('destinacion'), 'creation_year': project.get('ano_creacion'), 'creation_date': project.get('fecha_creacion'), 'created_by': project.get('creado_por'), 'status': project.get('estado'), 'last_modified': project.get('ultima_modificacion'), 'modified_by': project.get('modificado_por'), 'document_count': len(documents), 'report_generated': datetime.now(pytz.UTC).isoformat(), 'report_type': report_type } # Si es un informe detallado, incluir información de documentos if report_type == 'detailed': report['documents'] = [] for doc in documents: doc_info = { 'id': doc.get('id'), 'name': doc.get('document_id', '').split('_', 1)[1] if '_' in doc.get('document_id', '') else '', 'original_filename': doc.get('original_filename'), 'version_count': len(doc.get('versions', [])), } # Incluir información de la última versión if doc.get('versions'): latest_version = max(doc.get('versions', []), key=lambda v: v['version']) doc_info['latest_version'] = { 'version': latest_version.get('version'), 'created_at': latest_version.get('created_at'), 'created_by': latest_version.get('created_by'), 'description': latest_version.get('description'), 'file_size': latest_version.get('file_size'), 'download_count': len(latest_version.get('downloads', [])) } report['documents'].append(doc_info) return True, "Informe generado correctamente.", report