diff --git a/blueprints/admin/routes_companies.py b/blueprints/admin/routes_companies.py index 8158d0d..abc9800 100644 --- a/blueprints/admin/routes_companies.py +++ b/blueprints/admin/routes_companies.py @@ -5,6 +5,7 @@ Admin Routes - Companies CRUD operations for company management in admin panel. """ +import os import re import csv import logging @@ -15,7 +16,10 @@ from flask import render_template, request, redirect, url_for, flash, jsonify, R from flask_login import login_required, current_user from . import bp -from database import SessionLocal, Company, Category, User, Person, CompanyPerson, SystemRole +from database import ( + SessionLocal, Company, Category, User, Person, CompanyPerson, SystemRole, + CompanyWebsiteAnalysis, CompanySocialMedia, GBPAudit +) from utils.decorators import role_required # Logger @@ -94,7 +98,8 @@ def admin_companies(): current_status=status_filter, current_category=category_filter, current_quality=quality_filter, - search_query=search_query + search_query=search_query, + now=datetime.utcnow() ) finally: db.close() @@ -635,3 +640,117 @@ def company_settings(company_id): ) finally: db.close() + + +@bp.route('/companies//detail') +@login_required +@role_required(SystemRole.OFFICE_MANAGER) +def admin_company_detail(company_id): + """Admin company detail page with enrichment status and completeness score.""" + db = SessionLocal() + try: + company = db.query(Company).filter(Company.id == company_id).first() + if not company: + flash('Firma nie istnieje', 'error') + return redirect(url_for('admin.admin_companies')) + + # Users assigned to this company + users = db.query(User).filter(User.company_id == company_id).all() + + # --- Enrichment status --- + + # Registry data + registry_done = bool(company.ceidg_fetched_at or company.krs_fetched_at) + registry_source = None + registry_date = None + if company.krs_fetched_at: + registry_source = 'KRS' + registry_date = company.krs_fetched_at + elif company.ceidg_fetched_at: + registry_source = 'CEIDG' + registry_date = company.ceidg_fetched_at + + # Logo check + logo_path = os.path.join('static', 'img', 'companies', f'{company.slug}.webp') + logo_exists = os.path.isfile(logo_path) + + # SEO - latest website analysis + seo_analysis = db.query(CompanyWebsiteAnalysis).filter( + CompanyWebsiteAnalysis.company_id == company_id + ).order_by(CompanyWebsiteAnalysis.analyzed_at.desc()).first() + + # Social media count and latest date + social_accounts = db.query(CompanySocialMedia).filter( + CompanySocialMedia.company_id == company_id + ).order_by(CompanySocialMedia.verified_at.desc()).all() + social_count = len(social_accounts) + social_latest_date = social_accounts[0].verified_at if social_accounts else None + + # GBP - latest audit + gbp_audit = db.query(GBPAudit).filter( + GBPAudit.company_id == company_id + ).order_by(GBPAudit.audit_date.desc()).first() + + enrichment = { + 'registry': { + 'done': registry_done, + 'source': registry_source, + 'date': registry_date, + 'has_krs': bool(company.krs), + 'has_nip': bool(company.nip), + }, + 'logo': { + 'done': logo_exists, + 'path': f'/static/img/companies/{company.slug}.webp' if logo_exists else None, + }, + 'seo': { + 'done': seo_analysis is not None, + 'date': seo_analysis.analyzed_at if seo_analysis else None, + 'score': seo_analysis.seo_overall_score if seo_analysis else None, + }, + 'social': { + 'done': social_count > 0, + 'count': social_count, + 'date': social_latest_date, + }, + 'gbp': { + 'done': gbp_audit is not None, + 'date': gbp_audit.audit_date if gbp_audit else None, + 'score': gbp_audit.completeness_score if gbp_audit else None, + }, + } + + # --- Completeness score (12 fields) --- + fields = { + 'NIP': bool(company.nip), + 'Adres': bool(company.address_city), + 'Telefon': bool(company.phone), + 'Email': bool(company.email), + 'Strona WWW': bool(company.website), + 'Opis': bool(company.description_short), + 'Kategoria': bool(company.category_id), + 'Logo': enrichment['logo']['done'], + 'Dane urzędowe': enrichment['registry']['done'], + 'Audyt SEO': enrichment['seo']['done'], + 'Audyt Social': enrichment['social']['done'], + 'Audyt GBP': enrichment['gbp']['done'], + } + + completeness = { + 'score': int(sum(fields.values()) / len(fields) * 100), + 'fields': fields, + 'total': len(fields), + 'filled': sum(fields.values()), + } + + logger.info(f"Admin {current_user.email} viewed company detail: {company.name} (ID: {company_id})") + + return render_template( + 'admin/company_detail.html', + company=company, + enrichment=enrichment, + completeness=completeness, + users=users, + ) + finally: + db.close() diff --git a/templates/admin/companies.html b/templates/admin/companies.html index 8537919..7ab4e5f 100644 --- a/templates/admin/companies.html +++ b/templates/admin/companies.html @@ -170,6 +170,20 @@ .company-name { font-weight: 500; color: var(--text-primary); + text-decoration: none; + } + + .company-name:hover { + text-decoration: underline; + color: var(--primary); + } + + .badge-new { + background: #FEF3C7; + color: #92400E; + font-size: 9px; + margin-left: 4px; + vertical-align: middle; } .company-city { @@ -522,7 +536,11 @@
- {{ company.name }} + {{ company.name }} + {% set age_days = ((now - company.created_at).total_seconds() / 86400)|int if company.created_at and now else 999 %} + {% if age_days <= 7 %} + NOWA + {% endif %} {% if company.admin_notes %} {% endif %} diff --git a/templates/admin/company_detail.html b/templates/admin/company_detail.html new file mode 100644 index 0000000..86657a9 --- /dev/null +++ b/templates/admin/company_detail.html @@ -0,0 +1,815 @@ +{% extends "base.html" %} + +{% block title %}{{ company.name }} - Panel Admin{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+ +
+ +
+

+ {{ company.name }} + {{ company.status or 'pending' }} +

+ +
+
+ + +
+
+
{{ completeness.score }}%
+
Kompletność
+
+
+
+
+
+
+ {{ company.data_quality or 'basic' }} +
+
Jakość danych
+
+
+
{{ users|length }}
+
Użytkownicy
+
+
+
{{ company.created_at.strftime('%d.%m.%Y') if company.created_at else '---' }}
+
Utworzono
+
+
+ + +
+

Dane firmy

+
+
+ {% if enrichment.logo.path %} + Logo {{ company.name }} + {% else %} + + {% endif %} +
+
+

{{ company.name }}

+ {% if company.legal_name and company.legal_name != company.name %} +
{{ company.legal_name }}
+ {% endif %} +
+
+
+
+
+
NIP
+
{% if company.nip %}{{ company.nip }}{% else %}{% endif %}
+
+
+
KRS
+
{% if company.krs %}{{ company.krs }}{% else %}{% endif %}
+
+
+
REGON
+
{% if company.regon %}{{ company.regon }}{% else %}{% endif %}
+
+
+
Forma prawna
+
{{ company.legal_form or '' }}{%- if not company.legal_form %}{% endif %}
+
+
+
PKD
+
{% if company.pkd_code %}{{ company.pkd_code }}{% if company.pkd_description %} — {{ company.pkd_description }}{% endif %}{% else %}{% endif %}
+
+
+
Właściciel
+
{% if company.owner_first_name or company.owner_last_name %}{{ company.owner_first_name or '' }} {{ company.owner_last_name or '' }}{% else %}{% endif %}
+
+
+
+
+
Adres
+
+ {% if company.address_street or company.address_city %} + {{ company.address_street or '' }}{% if company.address_street and company.address_city %}, {% endif %} + {{ company.address_postal or '' }} {{ company.address_city or '' }} + {% else %} + + {% endif %} +
+
+
+
Email
+
{% if company.email %}{{ company.email }}{% else %}{% endif %}
+
+
+
Telefon
+
{{ company.phone or '' }}{%- if not company.phone %}{% endif %}
+
+
+
Strona WWW
+
{% if company.website %}{{ company.website }}{% else %}{% endif %}
+
+
+
Kategoria
+
{{ company.category.name if company.category else '' }}{%- if not company.category %}{% endif %}
+
+
+
Opis
+
{% if company.description_short %}{{ company.description_short[:200] }}{% if company.description_short|length > 200 %}...{% endif %}{% else %}{% endif %}
+
+
+
+ {% if company.admin_notes %} +
+
Notatki administracyjne
+
{{ company.admin_notes }}
+
+ {% endif %} +
+ + +
+

Workflow uzbrajania firmy

+ +
+ +
+ +
+ +
+
+ +

Dane urzędowe

+
+
+ {% if enrichment.registry.done %} + + Wykonano{% if enrichment.registry.date %} {{ enrichment.registry.date.strftime('%d.%m.%Y') }}{% endif %} + {% else %} + + Nie wykonano + {% endif %} +
+ {% if enrichment.registry.source %} +
Źródło: {{ enrichment.registry.source }}
+ {% endif %} + +
+ + +
+
+ +

Logo firmy

+
+
+ {% if enrichment.logo.done %} + + Pobrano + {% else %} + + Nie pobrano + {% endif %} +
+ +
+ + +
+
+ +

Audyt SEO

+
+
+ {% if enrichment.seo.done %} + + Wykonano{% if enrichment.seo.date %} {{ enrichment.seo.date.strftime('%d.%m.%Y') }}{% endif %} + {% else %} + + Nie wykonano + {% endif %} +
+ {% if enrichment.seo.score is not none and enrichment.seo.score is defined %} +
Wynik: {{ enrichment.seo.score }}/100
+ {% endif %} + +
+ + +
+
+ +

Audyt Social Media

+
+
+ {% if enrichment.social.done %} + + Wykonano ({{ enrichment.social.count }} profili) + {% else %} + + Nie wykonano + {% endif %} +
+ +
+ + +
+
+ +

Audyt GBP

+
+
+ {% if enrichment.gbp.done %} + + Wykonano{% if enrichment.gbp.date %} {{ enrichment.gbp.date.strftime('%d.%m.%Y') }}{% endif %} + {% else %} + + Nie wykonano + {% endif %} +
+ {% if enrichment.gbp.score is not none and enrichment.gbp.score is defined %} +
Wynik: {{ enrichment.gbp.score }}/100
+ {% endif %} + +
+
+
+ + +
+

Lista kompletności ({{ completeness.filled }}/{{ completeness.total }})

+
+ {% for field_name, is_filled in completeness.fields.items() %} +
+ {% if is_filled %} + + {% else %} + + {% endif %} + {{ field_name }} +
+ {% endfor %} +
+
+
+ + +
+{% endblock %} + +{% block extra_js %} + var csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); + + function showToast(message, type) { + var container = document.getElementById('toastContainer'); + var toast = document.createElement('div'); + toast.className = 'toast ' + (type || 'success'); + toast.innerHTML = '' + message + '' + + ''; + container.appendChild(toast); + setTimeout(function() { + toast.style.animation = 'slideOut 0.3s ease forwards'; + setTimeout(function() { toast.remove(); }, 300); + }, 5000); + } + + function runEnrichAction(btn, url, body) { + var original = btn.innerHTML; + btn.disabled = true; + btn.innerHTML = ' Pobieram...'; + return fetch(url, { + method: 'POST', + headers: {'Content-Type': 'application/json', 'X-CSRFToken': csrfToken}, + body: JSON.stringify(body || {}) + }) + .then(function(resp) { return resp.json(); }) + .then(function(data) { + if (data.success) { + showToast(data.message || 'Gotowe!', 'success'); + btn.innerHTML = ' Gotowe'; + return true; + } else { + showToast(data.error || 'Wystąpił błąd', 'error'); + btn.innerHTML = original; + btn.disabled = false; + return false; + } + }) + .catch(function() { + showToast('Błąd połączenia z serwerem', 'error'); + btn.innerHTML = original; + btn.disabled = false; + return false; + }); + } + + function fetchRegistry() { + var btn = document.getElementById('btn-registry'); + return runEnrichAction(btn, '/api/company/{{ company.id }}/enrich-registry', {}); + } + + function fetchLogo() { + var btn = document.getElementById('btn-logo'); + return runEnrichAction(btn, '/api/company/{{ company.id }}/fetch-logo', {action: 'fetch'}); + } + + function runSeoAudit() { + var btn = document.getElementById('btn-seo'); + return runEnrichAction(btn, '/api/seo/audit', {company_id: {{ company.id }}}); + } + + function runSocialAudit() { + var btn = document.getElementById('btn-social'); + return runEnrichAction(btn, '/api/social/audit', {company_id: {{ company.id }}}); + } + + function runGbpAudit() { + var btn = document.getElementById('btn-gbp'); + return runEnrichAction(btn, '/api/gbp/audit', {company_id: {{ company.id }}, fetch_google: true}); + } + + function armCompany() { + var masterBtn = document.getElementById('btn-arm'); + masterBtn.disabled = true; + masterBtn.innerHTML = ' Uzbrajam firmę...'; + + var steps = [ + {id: 'btn-registry', fn: fetchRegistry, skip: {{ 'true' if enrichment.registry.done else 'false' }}, requires: {{ 'true' if company.nip else 'false' }} }, + {id: 'btn-logo', fn: fetchLogo, skip: {{ 'true' if enrichment.logo.done else 'false' }}, requires: {{ 'true' if company.website else 'false' }} }, + {id: 'btn-seo', fn: runSeoAudit, skip: {{ 'true' if enrichment.seo.done else 'false' }}, requires: {{ 'true' if company.website else 'false' }} }, + {id: 'btn-social', fn: runSocialAudit, skip: {{ 'true' if enrichment.social.done else 'false' }}, requires: true }, + {id: 'btn-gbp', fn: runGbpAudit, skip: {{ 'true' if enrichment.gbp.done else 'false' }}, requires: true } + ]; + + var pending = steps.filter(function(s) { return !s.skip && s.requires; }); + + if (pending.length === 0) { + showToast('Wszystkie kroki zostały już wykonane!', 'success'); + masterBtn.disabled = false; + masterBtn.innerHTML = ' Uzbrój firmę'; + return; + } + + var idx = 0; + function runNext() { + if (idx >= pending.length) { + showToast('Uzbrajanie zakończone! Odświeżam stronę...', 'success'); + setTimeout(function() { location.reload(); }, 2000); + return; + } + var step = pending[idx]; + idx++; + masterBtn.innerHTML = ' Krok ' + idx + '/' + pending.length + '...'; + step.fn().then(function() { + runNext(); + }); + } + runNext(); + } +{% endblock %}