from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify from flask_login import login_required, current_user from flask_wtf import FlaskForm from wtforms import StringField, SelectField, TextAreaField, SubmitField from wtforms.validators import DataRequired, Length from services.project_service import ( create_project, update_project, get_project, delete_project, get_all_projects, get_project_children, get_project_document_count, filter_projects ) from services.schema_service import get_all_schemas from utils.security import permission_required # Definir Blueprint projects_bp = Blueprint('projects', __name__, url_prefix='/projects') # Formularios class ProjectForm(FlaskForm): """Formulario de proyecto.""" descripcion = StringField('Descripción', validators=[DataRequired(), Length(1, 100)]) cliente = StringField('Cliente', validators=[DataRequired(), Length(1, 100)]) destinacion = StringField('Destinación', validators=[Length(0, 100)]) esquema = SelectField('Esquema', validators=[DataRequired()]) proyecto_padre = SelectField('Proyecto Padre', validators=[]) submit = SubmitField('Guardar') class ProjectFilterForm(FlaskForm): """Formulario de filtrado de proyectos.""" cliente = StringField('Cliente') estado = SelectField('Estado', choices=[('', 'Todos'), ('activo', 'Activo'), ('inactivo', 'Inactivo')]) ano_inicio = StringField('Año Inicio') ano_fin = StringField('Año Fin') descripcion = StringField('Descripción') submit = SubmitField('Filtrar') # Rutas @projects_bp.route('/') @login_required def list(): """Listar proyectos.""" filter_form = ProjectFilterForm(request.args) # Obtener proyectos según filtros if request.args: projects = filter_projects(request.args) else: projects = get_all_projects() return render_template('projects/list.html', projects=projects, filter_form=filter_form) @projects_bp.route('/create', methods=['GET', 'POST']) @login_required @permission_required(1000) # Nivel mínimo para crear proyectos def create(): """Crear nuevo proyecto.""" form = ProjectForm() # Cargar opciones para esquemas schemas = get_all_schemas() form.esquema.choices = [(code, schema['descripcion']) for code, schema in schemas.items()] # Cargar opciones para proyectos padre projects = [(p['codigo'], p['descripcion']) for p in get_all_projects()] form.proyecto_padre.choices = [('', 'Ninguno')] + projects if form.validate_on_submit(): # Preparar datos del proyecto project_data = { 'descripcion': form.descripcion.data, 'cliente': form.cliente.data, 'destinacion': form.destinacion.data, 'esquema': form.esquema.data, 'proyecto_padre': form.proyecto_padre.data if form.proyecto_padre.data else None } # Crear proyecto success, message, project_id = create_project(project_data, current_user.username) if success: flash(message, 'success') return redirect(url_for('projects.view', project_id=project_id)) else: flash(message, 'danger') return render_template('projects/create.html', form=form) @projects_bp.route('/') @login_required def view(project_id): """Ver detalles de un proyecto.""" project = get_project(project_id) if not project: flash('Proyecto no encontrado.', 'danger') return redirect(url_for('projects.list')) # Obtener información adicional children = get_project_children(project_id) document_count = get_project_document_count(project_id) return render_template('projects/view.html', project=project, children=children, document_count=document_count) @projects_bp.route('//edit', methods=['GET', 'POST']) @login_required @permission_required(5000) # Nivel mínimo para editar proyectos def edit(project_id): """Editar un proyecto.""" project = get_project(project_id) if not project: flash('Proyecto no encontrado.', 'danger') return redirect(url_for('projects.list')) form = ProjectForm() # Cargar opciones para esquemas schemas = get_all_schemas() form.esquema.choices = [(code, schema['descripcion']) for code, schema in schemas.items()] # Cargar opciones para proyectos padre (excluyendo este proyecto y sus hijos) all_projects = get_all_projects() children_codes = [child['codigo'] for child in get_project_children(project_id)] available_projects = [(p['codigo'], p['descripcion']) for p in all_projects if p['codigo'] != project['codigo'] and p['codigo'] not in children_codes] form.proyecto_padre.choices = [('', 'Ninguno')] + available_projects if request.method == 'GET': # Cargar datos actuales form.descripcion.data = project['descripcion'] form.cliente.data = project['cliente'] form.destinacion.data = project.get('destinacion', '') form.esquema.data = project['esquema'] form.proyecto_padre.data = project.get('proyecto_padre', '') if form.validate_on_submit(): # Preparar datos actualizados project_data = { 'descripcion': form.descripcion.data, 'cliente': form.cliente.data, 'destinacion': form.destinacion.data, 'esquema': form.esquema.data, 'proyecto_padre': form.proyecto_padre.data if form.proyecto_padre.data else None } # Actualizar proyecto success, message = update_project(project_id, project_data, current_user.username) if success: flash(message, 'success') return redirect(url_for('projects.view', project_id=project_id)) else: flash(message, 'danger') return render_template('projects/edit.html', form=form, project=project) @projects_bp.route('//delete', methods=['POST']) @login_required @permission_required(9000) # Nivel alto para eliminar proyectos def delete(project_id): """Eliminar un proyecto (marcar como inactivo).""" success, message = delete_project(project_id) if success: flash(message, 'success') else: flash(message, 'danger') return redirect(url_for('projects.list')) @projects_bp.route('/api/list') @login_required def api_list(): """API para listar proyectos (para selects dinámicos).""" projects = get_all_projects() return jsonify([{ 'id': p['codigo'], 'text': p['descripcion'] } for p in projects])