feat: acople de schedule.view_reservation al BFF y creacion/asociacion de comisiones a materias
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from flask import Blueprint, jsonify, request
|
||||
from datetime import datetime
|
||||
import re
|
||||
from app.utils.jwt_decorators import jwt_required
|
||||
from app.models.user import User
|
||||
from app.models.role import Role, Permission, SYSTEM_MODULES
|
||||
@@ -196,12 +197,22 @@ def get_commissions():
|
||||
if search not in s_name and search not in c_code:
|
||||
continue
|
||||
|
||||
career_name = 'Carrera General'
|
||||
if c.subject and hasattr(c.subject, 'career') and c.subject.career:
|
||||
career_name = c.subject.career.name
|
||||
|
||||
commissions_data.append({
|
||||
'id': c.id,
|
||||
'code': c.code,
|
||||
'full_code': c.get_full_code() if hasattr(c, 'get_full_code') else c.code,
|
||||
'subject_id': c.subject_id,
|
||||
'subject_name': c.subject.name if c.subject else 'Sin materia',
|
||||
'subject_code': c.subject.code if c.subject else '-',
|
||||
'career_name': career_name,
|
||||
'semester': getattr(c, 'semester', '1C') or '1C',
|
||||
'year': getattr(c, 'year', 2026) or 2026,
|
||||
'teacher_id': c.teacher_id,
|
||||
'teacher_name': c.teacher_name if hasattr(c, 'teacher_name') else (c.teacher.name if c.teacher else None),
|
||||
'schedule': c.schedule or 'A coordinar',
|
||||
'shift': c.shift or 'Mañana',
|
||||
'max_students': c.max_students or 35,
|
||||
@@ -210,7 +221,16 @@ def get_commissions():
|
||||
'virtual_link': getattr(c, 'virtual_link', '') or ''
|
||||
})
|
||||
|
||||
subjects_data = [{'id': s.id, 'name': s.name, 'code': s.code} for s in subjects]
|
||||
subjects_data = []
|
||||
for s in subjects:
|
||||
c_name = s.career.name if (hasattr(s, 'career') and s.career) else 'Carrera General'
|
||||
subjects_data.append({
|
||||
'id': s.id,
|
||||
'name': s.name,
|
||||
'code': s.code,
|
||||
'career': c_name,
|
||||
'career_name': c_name
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
@@ -599,14 +619,14 @@ def create_commission():
|
||||
data = request.get_json(silent=True) or request.form.to_dict() or {}
|
||||
subject_id = data.get('subject_id')
|
||||
code = data.get('code', '').strip()
|
||||
semester = data.get('semester', '1C').strip()
|
||||
semester = (data.get('semester') or '1C').strip()
|
||||
year = int(data.get('year', datetime.now().year) or datetime.now().year)
|
||||
teacher_id = data.get('teacher_id')
|
||||
teacher_id = int(teacher_id) if teacher_id else None
|
||||
teacher_id = int(teacher_id) if (teacher_id and str(teacher_id).strip()) else None
|
||||
max_students = int(data.get('max_students', 35) or 35)
|
||||
schedule = data.get('schedule', '').strip()
|
||||
shift = data.get('shift', 'Mañana').strip()
|
||||
virtual_link = data.get('virtual_link', '').strip()
|
||||
schedule = (data.get('schedule') or '').strip()
|
||||
shift = (data.get('shift') or 'Mañana').strip()
|
||||
virtual_link = (data.get('virtual_link') or '').strip()
|
||||
active = data.get('active', True)
|
||||
if isinstance(active, str):
|
||||
active = active.lower() in ['true', '1', 'on']
|
||||
@@ -614,6 +634,38 @@ def create_commission():
|
||||
if not subject_id or not code:
|
||||
return jsonify({'error': 'ValidationError', 'message': 'La asignatura y código de comisión son obligatorios.'}), 400
|
||||
|
||||
subject = Subject.query.get(int(subject_id))
|
||||
if not subject:
|
||||
return jsonify({'error': 'NotFound', 'message': f'La asignatura con ID {subject_id} no existe.'}), 404
|
||||
|
||||
# Generar enlace automático si no fue provisto
|
||||
if not virtual_link:
|
||||
s_clean = re.sub(r'[^a-zA-Z0-9]', '', subject.code).lower()[:6] or 'subj'
|
||||
c_clean = re.sub(r'[^a-zA-Z0-9]', '', code).lower()[:4] or 'c1'
|
||||
virtual_link = f"https://meet.google.com/edu-{s_clean}-{c_clean}"
|
||||
|
||||
existing = Commission.query.filter_by(
|
||||
subject_id=int(subject_id),
|
||||
code=code,
|
||||
semester=semester,
|
||||
year=year
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
existing.teacher_id = teacher_id
|
||||
existing.max_students = max_students
|
||||
existing.schedule = schedule
|
||||
existing.shift = shift
|
||||
if virtual_link:
|
||||
existing.virtual_link = virtual_link
|
||||
existing.active = bool(active)
|
||||
db.session.commit()
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
'message': f'Comisión {code} asociada a {subject.name} actualizada exitosamente.',
|
||||
'commission': existing.to_dict()
|
||||
}), 200
|
||||
|
||||
commission = Commission(
|
||||
subject_id=int(subject_id),
|
||||
code=code,
|
||||
@@ -631,10 +683,11 @@ def create_commission():
|
||||
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
'message': 'Comisión creada exitosamente.',
|
||||
'message': f'Comisión {code} asociada exitosamente a {subject.name}.',
|
||||
'commission': commission.to_dict()
|
||||
}), 201
|
||||
|
||||
|
||||
@api_admin_bp.route('/commissions/<int:id>', methods=['PUT'])
|
||||
@jwt_required
|
||||
def update_commission(id):
|
||||
|
||||
@@ -103,27 +103,66 @@ def list_reservations():
|
||||
@api_reservations_bp.route('/<int:reservation_id>', methods=['GET'])
|
||||
@jwt_required
|
||||
def get_reservation(reservation_id: int):
|
||||
"""Obtiene el detalle de una reserva específica."""
|
||||
"""Obtiene el detalle de una reserva específica con sus relaciones completas."""
|
||||
r = reservation_service.get_reservation(reservation_id)
|
||||
if not r:
|
||||
return jsonify({'error': 'NotFound', 'message': f'Reserva {reservation_id} no encontrada.'}), 404
|
||||
|
||||
duration_min = int((r.end_time - r.start_time).total_seconds() / 60) if (r.start_time and r.end_time) else 120
|
||||
is_virt = bool(r.is_virtual or (r.classroom and r.classroom.is_virtual))
|
||||
|
||||
return jsonify({
|
||||
'id': r.id,
|
||||
'classroom_id': r.classroom_id,
|
||||
'classroom_name': r.classroom.code if r.classroom else None,
|
||||
'location_display': r.classroom.location_display if r.classroom else None,
|
||||
'location_display': getattr(r.classroom, 'location_display', r.classroom.code if r.classroom else None),
|
||||
'classroom': {
|
||||
'id': r.classroom.id,
|
||||
'code': r.classroom.code,
|
||||
'code_display': getattr(r.classroom, 'code_display', r.classroom.code),
|
||||
'building_name': getattr(r.classroom, 'building_name', getattr(r.classroom, 'building', 'Edificio Central')),
|
||||
'floor': r.classroom.floor,
|
||||
'floor_display': getattr(r.classroom, 'floor_display', f"Piso {r.classroom.floor}"),
|
||||
'capacity': r.classroom.capacity,
|
||||
'capacity_display': getattr(r.classroom, 'capacity_display', str(r.classroom.capacity)),
|
||||
'description': getattr(r.classroom, 'description', '') or '',
|
||||
'is_virtual': getattr(r.classroom, 'is_virtual', False)
|
||||
} if r.classroom else None,
|
||||
'commission_id': r.commission_id,
|
||||
'commission': {
|
||||
'id': r.commission.id,
|
||||
'code': r.commission.code,
|
||||
'full_code': r.commission.get_full_code() if hasattr(r.commission, 'get_full_code') else r.commission.code,
|
||||
'virtual_link': r.commission.virtual_link,
|
||||
'max_students': r.commission.max_students,
|
||||
'current_students': getattr(r.commission, 'current_students', 0),
|
||||
'subject': {
|
||||
'id': r.commission.subject.id,
|
||||
'name': r.commission.subject.name,
|
||||
'code': r.commission.subject.code
|
||||
} if (r.commission and r.commission.subject) else None
|
||||
} if r.commission else None,
|
||||
'teacher_id': r.user_id,
|
||||
'teacher_name': r.user.name if r.user else None,
|
||||
'user': {
|
||||
'id': r.user.id,
|
||||
'name': r.user.name,
|
||||
'first_name': getattr(r.user, 'first_name', r.user.name.split()[0] if r.user.name else 'Usuario'),
|
||||
'last_name': getattr(r.user, 'last_name', ' '.join(r.user.name.split()[1:]) if r.user.name and len(r.user.name.split()) > 1 else ''),
|
||||
'email': r.user.email
|
||||
} if r.user else None,
|
||||
'start_time': r.start_time.isoformat(),
|
||||
'end_time': r.end_time.isoformat(),
|
||||
'duration': duration_min,
|
||||
'purpose': r.purpose,
|
||||
'status': str(r.status),
|
||||
'shift': r.shift,
|
||||
'status': str(r.status.value if hasattr(r.status, 'value') else r.status),
|
||||
'shift': r.shift or 'Mañana',
|
||||
'virtual_link': r.virtual_link,
|
||||
'expected_attendees': r.expected_attendees,
|
||||
'notes': r.notes
|
||||
'effective_virtual_link': r.effective_virtual_link,
|
||||
'is_virtual': is_virt,
|
||||
'expected_attendees': r.expected_attendees or 30,
|
||||
'notes': r.notes or '',
|
||||
'can_be_cancelled': r.can_be_cancelled() if hasattr(r, 'can_be_cancelled') else True
|
||||
}), 200
|
||||
|
||||
@api_reservations_bp.route('', methods=['POST'])
|
||||
@@ -185,3 +224,24 @@ def cancel_reservation(reservation_id: int):
|
||||
return jsonify({'error': 'NotFound', 'message': f'Reserva {reservation_id} no encontrada.'}), 404
|
||||
|
||||
return jsonify({'message': f'Reserva {reservation_id} cancelada correctamente.'}), 200
|
||||
|
||||
@api_reservations_bp.route('/<int:reservation_id>/cancel', methods=['POST'])
|
||||
@jwt_required
|
||||
def cancel_reservation_post(reservation_id: int):
|
||||
"""Cancela una reserva (endpoint POST compatible con formularios)."""
|
||||
cancelled = reservation_service.cancel_reservation(reservation_id)
|
||||
if not cancelled:
|
||||
return jsonify({'error': 'NotFound', 'message': f'Reserva {reservation_id} no encontrada.'}), 404
|
||||
|
||||
return jsonify({'message': f'Reserva {reservation_id} cancelada correctamente.'}), 200
|
||||
|
||||
@api_reservations_bp.route('/<int:reservation_id>/confirm', methods=['POST'])
|
||||
@jwt_required
|
||||
def confirm_reservation(reservation_id: int):
|
||||
"""Confirma una reserva cambiando su estado a CONFIRMED."""
|
||||
confirmed = reservation_service.confirm_reservation(reservation_id)
|
||||
if not confirmed:
|
||||
return jsonify({'error': 'NotFound', 'message': f'Reserva {reservation_id} no encontrada.'}), 404
|
||||
|
||||
return jsonify({'message': f'Reserva {reservation_id} confirmada correctamente.'}), 200
|
||||
|
||||
|
||||
Reference in New Issue
Block a user