Arch/services/project_service.py

370 lines
11 KiB
Python
Raw Permalink Normal View History

2025-03-03 15:35:24 -03:00
import os
import json
from datetime import datetime
import pytz
from flask import current_app
from utils.file_utils import (
load_json_file,
save_json_file,
ensure_dir_exists,
get_next_id,
format_project_directory_name,
2025-03-03 15:35:24 -03:00
)
2025-03-03 15:35:24 -03:00
def create_project(project_data, creator_username):
"""
Crear un nuevo proyecto.
2025-03-03 15:35:24 -03:00
Args:
project_data (dict): Datos del proyecto
creator_username (str): Usuario que crea el proyecto
2025-03-03 15:35:24 -03:00
Returns:
tuple: (success, message, project_id)
"""
# Validar datos obligatorios
required_fields = ["descripcion", "cliente", "esquema"]
2025-03-03 15:35:24 -03:00
for field in required_fields:
if field not in project_data or not project_data[field]:
return False, f"El campo '{field}' es obligatorio.", None
2025-03-03 15:35:24 -03:00
# Obtener siguiente ID de proyecto
project_id = get_next_id("project")
2025-03-03 15:35:24 -03:00
# Crear código de proyecto (PROJ001, etc.)
project_code = f"PROJ{project_id:03d}"
2025-03-03 15:35:24 -03:00
# Preparar directorio del proyecto
storage_path = current_app.config["STORAGE_PATH"]
dir_name = format_project_directory_name(project_id, project_data["descripcion"])
project_dir = os.path.join(storage_path, "projects", dir_name)
2025-03-03 15:35:24 -03:00
# Verificar si ya existe
if os.path.exists(project_dir):
return False, "Ya existe un proyecto con esa descripción.", None
2025-03-03 15:35:24 -03:00
# Crear directorios
ensure_dir_exists(project_dir)
ensure_dir_exists(os.path.join(project_dir, "documents"))
2025-03-03 15:35:24 -03:00
# Crear metadatos del proyecto
project_meta = {
"codigo": project_code,
"proyecto_padre": project_data.get("proyecto_padre"),
"esquema": project_data["esquema"],
"descripcion": project_data["descripcion"],
"cliente": project_data["cliente"],
"destinacion": project_data.get("destinacion", ""),
"ano_creacion": datetime.now().year,
"fecha_creacion": datetime.now(pytz.UTC).isoformat(),
"creado_por": creator_username,
"estado": "activo",
"ultima_modificacion": datetime.now(pytz.UTC).isoformat(),
"modificado_por": creator_username,
2025-03-03 15:35:24 -03:00
}
2025-03-03 15:35:24 -03:00
# Guardar metadatos
meta_file = os.path.join(project_dir, "project_meta.json")
2025-03-03 15:35:24 -03:00
save_json_file(meta_file, project_meta)
2025-03-03 15:35:24 -03:00
# Guardar permisos del proyecto (inicialmente vacío)
permissions_file = os.path.join(project_dir, "permissions.json")
2025-03-03 15:35:24 -03:00
save_json_file(permissions_file, {})
2025-03-03 15:35:24 -03:00
# Copiar el esquema seleccionado
schema_file = os.path.join(storage_path, "schemas", "schema.json")
2025-03-03 15:35:24 -03:00
schemas = load_json_file(schema_file, {})
if project_data["esquema"] in schemas:
project_schema_file = os.path.join(project_dir, "schema.json")
save_json_file(project_schema_file, schemas[project_data["esquema"]])
2025-03-03 15:35:24 -03:00
return True, "Proyecto creado correctamente.", project_id
2025-03-03 15:35:24 -03:00
def update_project(project_id, project_data, modifier_username):
"""
Actualizar un proyecto existente.
2025-03-03 15:35:24 -03:00
Args:
project_id (int): ID del proyecto
project_data (dict): Datos actualizados
modifier_username (str): Usuario que modifica
2025-03-03 15:35:24 -03:00
Returns:
tuple: (success, message)
"""
# Buscar el proyecto
project_dir = find_project_directory(project_id)
2025-03-03 15:35:24 -03:00
if not project_dir:
return False, f"No se encontró el proyecto con ID {project_id}."
2025-03-03 15:35:24 -03:00
# Cargar metadatos actuales
meta_file = os.path.join(project_dir, "project_meta.json")
2025-03-03 15:35:24 -03:00
current_meta = load_json_file(meta_file)
2025-03-03 15:35:24 -03:00
# Actualizar campos
for key, value in project_data.items():
if key in current_meta and key not in [
"codigo",
"fecha_creacion",
"creado_por",
"ano_creacion",
]:
2025-03-03 15:35:24 -03:00
current_meta[key] = value
2025-03-03 15:35:24 -03:00
# Actualizar metadatos de modificación
current_meta["ultima_modificacion"] = datetime.now(pytz.UTC).isoformat()
current_meta["modificado_por"] = modifier_username
2025-03-03 15:35:24 -03:00
# Guardar metadatos actualizados
save_json_file(meta_file, current_meta)
2025-03-03 15:35:24 -03:00
return True, "Proyecto actualizado correctamente."
2025-03-03 15:35:24 -03:00
def get_project(project_id):
"""
Obtener información de un proyecto.
2025-03-03 15:35:24 -03:00
Args:
project_id (int): ID del proyecto
2025-03-03 15:35:24 -03:00
Returns:
dict: Datos del proyecto o None si no existe
"""
project_dir = find_project_directory(project_id)
2025-03-03 15:35:24 -03:00
if not project_dir:
return None
2025-03-03 15:35:24 -03:00
# Cargar metadatos
meta_file = os.path.join(project_dir, "project_meta.json")
2025-03-03 15:35:24 -03:00
project_meta = load_json_file(meta_file)
2025-03-03 15:35:24 -03:00
# Agregar la ruta del directorio
project_meta["directory"] = os.path.basename(project_dir)
2025-03-03 15:35:24 -03:00
return project_meta
2025-03-03 15:35:24 -03:00
def delete_project(project_id):
"""
Eliminar un proyecto (marcar como inactivo).
2025-03-03 15:35:24 -03:00
Args:
project_id (int): ID del proyecto
2025-03-03 15:35:24 -03:00
Returns:
tuple: (success, message)
"""
project_dir = find_project_directory(project_id)
2025-03-03 15:35:24 -03:00
if not project_dir:
return False, f"No se encontró el proyecto con ID {project_id}."
2025-03-03 15:35:24 -03:00
# Cargar metadatos
meta_file = os.path.join(project_dir, "project_meta.json")
2025-03-03 15:35:24 -03:00
project_meta = load_json_file(meta_file)
2025-03-03 15:35:24 -03:00
# Marcar como inactivo (no eliminar físicamente)
project_meta["estado"] = "inactivo"
project_meta["ultima_modificacion"] = datetime.now(pytz.UTC).isoformat()
2025-03-03 15:35:24 -03:00
# Guardar metadatos actualizados
save_json_file(meta_file, project_meta)
2025-03-03 15:35:24 -03:00
return True, "Proyecto marcado como inactivo."
2025-03-03 15:35:24 -03:00
def get_all_projects(include_inactive=False):
"""
Obtener todos los proyectos.
2025-03-03 15:35:24 -03:00
Args:
include_inactive (bool): Incluir proyectos inactivos
2025-03-03 15:35:24 -03:00
Returns:
list: Lista de proyectos
"""
storage_path = current_app.config["STORAGE_PATH"]
projects_dir = os.path.join(storage_path, "projects")
2025-03-03 15:35:24 -03:00
projects = []
2025-03-03 15:35:24 -03:00
# Iterar sobre directorios de proyectos
for dir_name in os.listdir(projects_dir):
if dir_name.startswith("@"): # Formato de directorio de proyecto
2025-03-03 15:35:24 -03:00
project_dir = os.path.join(projects_dir, dir_name)
2025-03-03 15:35:24 -03:00
if os.path.isdir(project_dir):
meta_file = os.path.join(project_dir, "project_meta.json")
2025-03-03 15:35:24 -03:00
if os.path.exists(meta_file):
project_meta = load_json_file(meta_file)
2025-03-03 15:35:24 -03:00
# Incluir solo si está activo o se solicitan inactivos
if include_inactive or project_meta.get("estado") == "activo":
2025-03-03 15:35:24 -03:00
# Agregar la ruta del directorio
project_meta["directory"] = dir_name
2025-03-03 15:35:24 -03:00
projects.append(project_meta)
2025-03-03 15:35:24 -03:00
return projects
2025-03-03 15:35:24 -03:00
def find_project_directory(project_id):
"""
Encontrar el directorio de un proyecto por su ID.
2025-03-03 15:35:24 -03:00
Args:
project_id (int): ID del proyecto
2025-03-03 15:35:24 -03:00
Returns:
str: Ruta al directorio o None si no se encuentra
"""
storage_path = current_app.config["STORAGE_PATH"]
projects_dir = os.path.join(storage_path, "projects")
2025-03-03 15:35:24 -03:00
# Prefijo a buscar en nombres de directorios
prefix = f"@{int(project_id):03d}_@"
2025-03-03 15:35:24 -03:00
for dir_name in os.listdir(projects_dir):
if dir_name.startswith(prefix):
return os.path.join(projects_dir, dir_name)
2025-03-03 15:35:24 -03:00
return None
2025-03-03 15:35:24 -03:00
def get_project_children(project_id):
"""
Obtener proyectos hijos de un proyecto.
2025-03-03 15:35:24 -03:00
Args:
project_id (int): ID del proyecto padre
2025-03-03 15:35:24 -03:00
Returns:
list: Lista de proyectos hijos
"""
all_projects = get_all_projects()
project_code = f"PROJ{int(project_id):03d}"
2025-03-03 15:35:24 -03:00
# Filtrar proyectos con este padre
children = [p for p in all_projects if p.get("proyecto_padre") == project_code]
2025-03-03 15:35:24 -03:00
return children
2025-03-03 15:35:24 -03:00
def get_project_document_count(project_id):
"""
Contar documentos en un proyecto.
2025-03-03 15:35:24 -03:00
Args:
project_id (int): ID del proyecto
2025-03-03 15:35:24 -03:00
Returns:
int: Número de documentos
"""
project_dir = find_project_directory(project_id)
2025-03-03 15:35:24 -03:00
if not project_dir:
return 0
documents_dir = os.path.join(project_dir, "documents")
2025-03-03 15:35:24 -03:00
if not os.path.exists(documents_dir):
return 0
2025-03-03 15:35:24 -03:00
# Contar directorios de documentos
count = 0
for item in os.listdir(documents_dir):
if os.path.isdir(os.path.join(documents_dir, item)) and item.startswith("@"):
2025-03-03 15:35:24 -03:00
count += 1
2025-03-03 15:35:24 -03:00
return count
2025-03-03 15:35:24 -03:00
def filter_projects(filter_params):
"""
Filtrar proyectos según los parámetros proporcionados.
2025-03-03 15:35:24 -03:00
Args:
filter_params (dict): Diccionario con parámetros de filtrado
- cliente: Nombre de cliente
- estado: Estado del proyecto ('activo', 'inactivo')
- ano_inicio: Año de inicio para filtrar
- ano_fin: Año final para filtrar
- descripcion: Término de búsqueda en descripción
2025-03-03 15:35:24 -03:00
Returns:
list: Lista de proyectos que cumplen los criterios
"""
# Obtener todos los proyectos (incluyendo inactivos si se solicitan)
include_inactive = filter_params.get("estado") == "inactivo"
2025-03-03 15:35:24 -03:00
all_projects = get_all_projects(include_inactive)
filtered_projects = []
2025-03-03 15:35:24 -03:00
for project in all_projects:
# Filtrar por cliente
if "cliente" in filter_params and filter_params["cliente"]:
if project["cliente"] != filter_params["cliente"]:
2025-03-03 15:35:24 -03:00
continue
2025-03-03 15:35:24 -03:00
# Filtrar por estado
if "estado" in filter_params and filter_params["estado"]:
if project["estado"] != filter_params["estado"]:
2025-03-03 15:35:24 -03:00
continue
2025-03-03 15:35:24 -03:00
# Filtrar por año de creación (rango)
if "ano_inicio" in filter_params and filter_params["ano_inicio"]:
if project["ano_creacion"] < int(filter_params["ano_inicio"]):
2025-03-03 15:35:24 -03:00
continue
if "ano_fin" in filter_params and filter_params["ano_fin"]:
if project["ano_creacion"] > int(filter_params["ano_fin"]):
2025-03-03 15:35:24 -03:00
continue
2025-03-03 15:35:24 -03:00
# Filtrar por término en descripción
if "descripcion" in filter_params and filter_params["descripcion"]:
if (
filter_params["descripcion"].lower()
not in project["descripcion"].lower()
):
2025-03-03 15:35:24 -03:00
continue
2025-03-03 15:35:24 -03:00
# Si pasó todos los filtros, agregar a la lista
filtered_projects.append(project)
return filtered_projects
def archive_project(project_id, archiver_username):
"""
Archivar un proyecto (marcar como archivado).
Args:
project_id (int): ID del proyecto
archiver_username (str): Usuario que archiva el proyecto
Returns:
tuple: (success, message)
"""
project_dir = find_project_directory(project_id)
if not project_dir:
return False, f"No se encontró el proyecto con ID {project_id}."
# Cargar metadatos
meta_file = os.path.join(project_dir, "project_meta.json")
project_meta = load_json_file(meta_file)
# Marcar como archivado
project_meta["estado"] = "archivado"
project_meta["ultima_modificacion"] = datetime.now(pytz.UTC).isoformat()
project_meta["modificado_por"] = archiver_username
# Guardar metadatos actualizados
save_json_file(meta_file, project_meta)
return True, "Proyecto archivado correctamente."