push
This commit is contained in:
204
app/dashboard/routes.py
Normal file
204
app/dashboard/routes.py
Normal file
@@ -0,0 +1,204 @@
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from flask import Blueprint, current_app, jsonify, redirect, render_template, request, url_for
|
||||
from flask_login import current_user, login_required
|
||||
from sqlalchemy import extract
|
||||
|
||||
from app.extensions import csrf
|
||||
from app.models.invoice import Invoice
|
||||
from app.models.sync_log import SyncLog
|
||||
from app.services.company_service import CompanyService
|
||||
from app.services.health_service import HealthService
|
||||
from app.services.redis_service import RedisService
|
||||
from app.services.settings_service import SettingsService
|
||||
from app.services.sync_service import SyncService
|
||||
|
||||
bp = Blueprint('dashboard', __name__)
|
||||
|
||||
|
||||
def _load_dashboard_summary(company_id: int):
|
||||
cache_key = f'dashboard.summary.company.{company_id}'
|
||||
cached = RedisService.get_json(cache_key) or {}
|
||||
base = Invoice.query.filter_by(company_id=company_id)
|
||||
today = date.today()
|
||||
|
||||
if not cached:
|
||||
month_invoices = base.filter(
|
||||
extract('month', Invoice.issue_date) == today.month,
|
||||
extract('year', Invoice.issue_date) == today.year,
|
||||
).order_by(Invoice.issue_date.desc(), Invoice.id.desc()).all()
|
||||
cached = {
|
||||
'month_invoice_ids': [invoice.id for invoice in month_invoices],
|
||||
'unread': base.filter_by(is_unread=True).count(),
|
||||
'totals': {
|
||||
'net': str(sum(Decimal(invoice.net_amount) for invoice in month_invoices)),
|
||||
'vat': str(sum(Decimal(invoice.vat_amount) for invoice in month_invoices)),
|
||||
'gross': str(sum(Decimal(invoice.gross_amount) for invoice in month_invoices)),
|
||||
},
|
||||
'recent_invoice_ids': [invoice.id for invoice in base.order_by(Invoice.created_at.desc(), Invoice.id.desc()).limit(200).all()],
|
||||
}
|
||||
RedisService.set_json(cache_key, cached, ttl=300)
|
||||
|
||||
month_ids = cached.get('month_invoice_ids', [])
|
||||
month_invoices = Invoice.query.filter(Invoice.id.in_(month_ids)).all() if month_ids else []
|
||||
month_invoices.sort(key=lambda item: month_ids.index(item.id) if item.id in month_ids else 9999)
|
||||
|
||||
totals = {
|
||||
'net': Decimal(str(cached.get('totals', {}).get('net', '0'))),
|
||||
'vat': Decimal(str(cached.get('totals', {}).get('vat', '0'))),
|
||||
'gross': Decimal(str(cached.get('totals', {}).get('gross', '0'))),
|
||||
}
|
||||
return cached, month_invoices, totals
|
||||
|
||||
|
||||
@bp.route('/')
|
||||
@login_required
|
||||
def index():
|
||||
company = CompanyService.get_current_company()
|
||||
health_service = HealthService()
|
||||
health = health_service.get_cached_status(company.id if company else None) or health_service.get_status(company_id=company.id if company else None)
|
||||
|
||||
if not company:
|
||||
return render_template(
|
||||
'dashboard/index.html',
|
||||
company=None,
|
||||
month_invoices=[],
|
||||
unread=0,
|
||||
totals={'net': Decimal('0'), 'vat': Decimal('0'), 'gross': Decimal('0')},
|
||||
recent_invoices=[],
|
||||
last_sync_display='brak',
|
||||
sync_status='inactive',
|
||||
health=health,
|
||||
current_user=current_user,
|
||||
recent_pagination={'page': 1, 'pages': 1, 'has_prev': False, 'has_next': False, 'prev_num': 1, 'next_num': 1},
|
||||
payment_details_map={},
|
||||
redis_fallback=(health.get('redis') == 'fallback'),
|
||||
)
|
||||
|
||||
read_only = SettingsService.read_only_enabled(company_id=company.id)
|
||||
base = Invoice.query.filter_by(company_id=company.id)
|
||||
last_sync_raw = SettingsService.get('ksef.last_sync_at', None, company_id=company.id)
|
||||
last_sync = None
|
||||
if isinstance(last_sync_raw, str) and last_sync_raw.strip():
|
||||
try:
|
||||
last_sync = datetime.fromisoformat(last_sync_raw.replace('Z', '+00:00'))
|
||||
except Exception:
|
||||
last_sync = last_sync_raw
|
||||
elif last_sync_raw:
|
||||
last_sync = last_sync_raw
|
||||
if not last_sync:
|
||||
latest_log = SyncLog.query.filter_by(company_id=company.id, status='finished').order_by(SyncLog.finished_at.desc()).first()
|
||||
last_sync = latest_log.finished_at if latest_log and latest_log.finished_at else None
|
||||
|
||||
cached, month_invoices, totals = _load_dashboard_summary(company.id)
|
||||
unread = cached.get('unread', 0)
|
||||
recent_ids = cached.get('recent_invoice_ids', [])
|
||||
per_page = 10
|
||||
total_recent = len(recent_ids)
|
||||
total_pages = max((total_recent + per_page - 1) // per_page, 1)
|
||||
dashboard_page = min(max(request.args.get('dashboard_page', 1, type=int), 1), total_pages)
|
||||
start = (dashboard_page - 1) * per_page
|
||||
end = start + per_page
|
||||
current_ids = recent_ids[start:end]
|
||||
recent_invoices = Invoice.query.filter(Invoice.id.in_(current_ids)).all() if current_ids else []
|
||||
recent_invoices.sort(key=lambda item: current_ids.index(item.id) if item.id in current_ids else 9999)
|
||||
recent_pagination = {
|
||||
'page': dashboard_page,
|
||||
'pages': total_pages,
|
||||
'has_prev': dashboard_page > 1,
|
||||
'has_next': end < total_recent,
|
||||
'prev_num': dashboard_page - 1,
|
||||
'next_num': dashboard_page + 1,
|
||||
}
|
||||
|
||||
from app.services.invoice_service import InvoiceService
|
||||
payment_details_map = {invoice.id: InvoiceService().resolve_payment_details(invoice) for invoice in recent_invoices}
|
||||
last_sync_display = last_sync.strftime('%Y-%m-%d %H:%M:%S') if hasattr(last_sync, 'strftime') else (last_sync or 'brak')
|
||||
|
||||
return render_template(
|
||||
'dashboard/index.html',
|
||||
company=company,
|
||||
month_invoices=month_invoices,
|
||||
unread=unread,
|
||||
totals=totals,
|
||||
recent_invoices=recent_invoices,
|
||||
recent_pagination=recent_pagination,
|
||||
payment_details_map=payment_details_map,
|
||||
last_sync_display=last_sync_display,
|
||||
last_sync_raw=last_sync,
|
||||
sync_status=SettingsService.get('ksef.status', 'inactive', company_id=company.id),
|
||||
health=health,
|
||||
read_only=read_only,
|
||||
redis_fallback=(health.get('redis') == 'fallback'),
|
||||
)
|
||||
|
||||
|
||||
@bp.route('/switch-company/<int:company_id>')
|
||||
@login_required
|
||||
def switch_company(company_id):
|
||||
CompanyService.set_active_company(company_id)
|
||||
return redirect(url_for('dashboard.index'))
|
||||
|
||||
|
||||
@bp.post('/sync/manual')
|
||||
@login_required
|
||||
def manual_sync():
|
||||
company = CompanyService.get_current_company()
|
||||
if not company:
|
||||
return redirect(url_for('dashboard.index'))
|
||||
app = current_app._get_current_object()
|
||||
log_id = SyncService.start_manual_sync_async(app, company.id)
|
||||
return redirect(url_for('dashboard.index', started=log_id))
|
||||
|
||||
|
||||
@bp.route('/sync/status')
|
||||
@login_required
|
||||
@csrf.exempt
|
||||
def sync_status():
|
||||
company = CompanyService.get_current_company()
|
||||
if not company:
|
||||
return jsonify({'status': 'no_company'})
|
||||
log = SyncLog.query.filter_by(company_id=company.id).order_by(SyncLog.started_at.desc()).first()
|
||||
if not log:
|
||||
return jsonify({'status': 'idle'})
|
||||
return jsonify(
|
||||
{
|
||||
'status': log.status,
|
||||
'message': log.message,
|
||||
'processed': log.processed,
|
||||
'created': log.created,
|
||||
'updated': log.updated,
|
||||
'errors': log.errors,
|
||||
'total': log.total,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@bp.post('/sync/start')
|
||||
@login_required
|
||||
def sync_start():
|
||||
company = CompanyService.get_current_company()
|
||||
if not company:
|
||||
return jsonify({'error': 'no_company'}), 400
|
||||
app = current_app._get_current_object()
|
||||
log_id = SyncService.start_manual_sync_async(app, company.id)
|
||||
return jsonify({'log_id': log_id})
|
||||
|
||||
|
||||
@bp.get('/sync/status/<int:log_id>')
|
||||
@login_required
|
||||
def sync_status_by_id(log_id):
|
||||
log = SyncLog.query.get_or_404(log_id)
|
||||
total = log.total or 0
|
||||
progress = int((log.processed / total) * 100) if total else (100 if log.status == 'finished' else 0)
|
||||
return jsonify({
|
||||
'status': log.status,
|
||||
'message': log.message,
|
||||
'processed': log.processed,
|
||||
'created': log.created,
|
||||
'updated': log.updated,
|
||||
'errors': log.errors,
|
||||
'total': total,
|
||||
'progress': progress,
|
||||
})
|
||||
Reference in New Issue
Block a user