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
New analyze_roadmap_with_ai() function sends existing milestones and recent knowledge facts to Gemini for comprehensive analysis. Returns new milestone suggestions, status update recommendations, and identified roadmap gaps. Adds PATCH endpoint for milestone status updates and tabbed UI modal. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
256 lines
8.1 KiB
Python
256 lines
8.1 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/ai-analyze')
|
|
@login_required
|
|
@role_required(SystemRole.ADMIN)
|
|
def api_zopk_timeline_ai_analyze():
|
|
"""API - AI analysis of roadmap: new milestones, status updates, gaps."""
|
|
|
|
from zopk_knowledge_service import analyze_roadmap_with_ai
|
|
|
|
db = SessionLocal()
|
|
try:
|
|
result = analyze_roadmap_with_ai(db)
|
|
return jsonify(result)
|
|
except Exception as e:
|
|
logger.error(f"AI analyze error: {e}")
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/zopk-api/milestones/<int:milestone_id>/status', methods=['PATCH'])
|
|
@login_required
|
|
@role_required(SystemRole.ADMIN)
|
|
def api_zopk_milestone_update_status(milestone_id):
|
|
"""API - update milestone status."""
|
|
|
|
db = SessionLocal()
|
|
try:
|
|
milestone = db.query(ZOPKMilestone).get(milestone_id)
|
|
if not milestone:
|
|
return jsonify({'error': 'Not found'}), 404
|
|
|
|
data = request.get_json()
|
|
new_status = data.get('status')
|
|
if new_status not in ('planned', 'in_progress', 'completed', 'delayed', 'cancelled'):
|
|
return jsonify({'error': 'Invalid status'}), 400
|
|
|
|
milestone.status = new_status
|
|
db.commit()
|
|
|
|
return jsonify({'success': True, 'milestone_id': milestone_id, 'status': new_status})
|
|
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()
|