nordabiz/blueprints/admin/routes_zopk_timeline.py
Maciej Pienczyn f2fc1b89ec
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
refactor(rbac): Complete RBAC migration - 154/154 admin routes protected
- Add @role_required to 2 missing routes (krs_api PDF download, zopk milestones)
- Add role-based menu visibility in admin bar (hide Users, Security, Benefits,
  Model Comparison, Debug from OFFICE_MANAGER users)
- Inject SystemRole into Jinja2 context processor for template role checks
- Replace is_admin checkbox with role select dropdown in user creation form
- Migrate routes.py and routes_users_api.py from is_admin to SystemRole-based
  role assignment via set_role()
- Add deprecation notice to is_admin database column
- Add 23 RBAC unit tests (hierarchy, has_role, set_role, permissions)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 21:36:14 +01:00

209 lines
6.7 KiB
Python

"""
ZOPK Timeline Routes - Admin blueprint
Migrated from app.py as part of the blueprint refactoring.
Contains routes for ZOPK timeline and milestones management.
"""
import logging
from datetime import datetime
from flask import flash, jsonify, redirect, render_template, request, url_for
from flask_login import current_user, login_required
from database import (
SessionLocal,
SystemRole,
ZOPKMilestone
)
from utils.decorators import role_required
from . import bp
logger = logging.getLogger(__name__)
@bp.route('/zopk/timeline')
@login_required
@role_required(SystemRole.ADMIN)
def admin_zopk_timeline():
"""Panel Timeline ZOPK."""
return render_template('admin/zopk_timeline.html')
@bp.route('/zopk-api/milestones')
@login_required
@role_required(SystemRole.OFFICE_MANAGER)
def api_zopk_milestones():
"""API - lista kamieni milowych ZOPK."""
db = SessionLocal()
try:
milestones = db.query(ZOPKMilestone).order_by(ZOPKMilestone.target_date).all()
return jsonify({
'success': True,
'milestones': [{
'id': m.id,
'title': m.title,
'description': m.description,
'category': m.category,
'target_date': m.target_date.isoformat() if m.target_date else None,
'actual_date': m.actual_date.isoformat() if m.actual_date else None,
'status': m.status,
'source_url': m.source_url
} for m in milestones]
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()
@bp.route('/zopk-api/milestones', methods=['POST'])
@login_required
@role_required(SystemRole.ADMIN)
def api_zopk_milestone_create():
"""API - utworzenie kamienia milowego."""
db = SessionLocal()
try:
data = request.get_json()
milestone = ZOPKMilestone(
title=data['title'],
description=data.get('description'),
category=data.get('category', 'other'),
target_date=datetime.strptime(data['target_date'], '%Y-%m-%d').date() if data.get('target_date') else None,
actual_date=datetime.strptime(data['actual_date'], '%Y-%m-%d').date() if data.get('actual_date') else None,
status=data.get('status', 'planned'),
source_url=data.get('source_url'),
source_news_id=data.get('source_news_id')
)
db.add(milestone)
db.commit()
return jsonify({'success': True, 'id': milestone.id})
except Exception as e:
db.rollback()
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()
@bp.route('/zopk-api/milestones/<int:milestone_id>', methods=['PUT'])
@login_required
@role_required(SystemRole.ADMIN)
def api_zopk_milestone_update(milestone_id):
"""API - aktualizacja kamienia milowego."""
db = SessionLocal()
try:
milestone = db.query(ZOPKMilestone).get(milestone_id)
if not milestone:
return jsonify({'error': 'Not found'}), 404
data = request.get_json()
if 'title' in data:
milestone.title = data['title']
if 'description' in data:
milestone.description = data['description']
if 'category' in data:
milestone.category = data['category']
if 'target_date' in data:
milestone.target_date = datetime.strptime(data['target_date'], '%Y-%m-%d').date() if data['target_date'] else None
if 'actual_date' in data:
milestone.actual_date = datetime.strptime(data['actual_date'], '%Y-%m-%d').date() if data['actual_date'] else None
if 'status' in data:
milestone.status = data['status']
if 'source_url' in data:
milestone.source_url = data['source_url']
db.commit()
return jsonify({'success': True})
except Exception as e:
db.rollback()
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()
@bp.route('/zopk-api/milestones/<int:milestone_id>', methods=['DELETE'])
@login_required
@role_required(SystemRole.ADMIN)
def api_zopk_milestone_delete(milestone_id):
"""API - usunięcie kamienia milowego."""
db = SessionLocal()
try:
milestone = db.query(ZOPKMilestone).get(milestone_id)
if not milestone:
return jsonify({'error': 'Not found'}), 404
db.delete(milestone)
db.commit()
return jsonify({'success': True})
except Exception as e:
db.rollback()
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()
@bp.route('/zopk-api/timeline/suggestions')
@login_required
@role_required(SystemRole.ADMIN)
def api_zopk_timeline_suggestions():
"""API - sugestie kamieni milowych z bazy wiedzy."""
from zopk_knowledge_service import get_timeline_suggestions
limit = request.args.get('limit', 30, type=int)
only_verified = request.args.get('only_verified', 'false').lower() == 'true'
use_ai = request.args.get('use_ai', 'false').lower() == 'true'
db = SessionLocal()
try:
result = get_timeline_suggestions(db, limit=limit, only_verified=only_verified)
if result['success'] and use_ai and result.get('suggestions'):
from zopk_knowledge_service import categorize_milestones_with_ai
result['suggestions'] = categorize_milestones_with_ai(db, result['suggestions'])
return jsonify(result)
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()
@bp.route('/zopk-api/timeline/suggestions/approve', methods=['POST'])
@login_required
@role_required(SystemRole.ADMIN)
def api_zopk_timeline_suggestion_approve():
"""API - zatwierdzenie sugestii i utworzenie kamienia milowego."""
from zopk_knowledge_service import create_milestone_from_suggestion
data = request.get_json()
if not data:
return jsonify({'error': 'No data provided'}), 400
fact_id = data.get('fact_id')
if not fact_id:
return jsonify({'error': 'fact_id is required'}), 400
db = SessionLocal()
try:
result = create_milestone_from_suggestion(
db_session=db,
fact_id=fact_id,
title=data.get('title', 'Kamień milowy'),
description=data.get('description'),
category=data.get('category', 'other'),
target_date=data.get('target_date'),
status=data.get('status', 'planned'),
source_url=data.get('source_url')
)
return jsonify(result)
except Exception as e:
db.rollback()
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()