feat(company): Add registry data enrichment button for admins
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
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
Adds "Pobierz dane urzędowe" button on company detail page (admin-only) that fetches data from KRS, Biała Lista VAT, or CEIDG registries. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d6bcf88f1d
commit
6d436bb0ae
@ -18,11 +18,12 @@ from flask import jsonify, request, current_app
|
||||
from flask_login import current_user, login_required
|
||||
|
||||
from database import (
|
||||
SessionLocal, Company, User, Person, CompanyPerson, CompanyAIInsights, AiEnrichmentProposal
|
||||
SessionLocal, Company, User, Person, CompanyPerson, CompanyPKD, CompanyAIInsights, AiEnrichmentProposal
|
||||
)
|
||||
from datetime import timedelta
|
||||
import gemini_service
|
||||
import krs_api_service
|
||||
from ceidg_api_service import fetch_ceidg_by_nip
|
||||
from . import bp
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -349,6 +350,162 @@ def api_refresh_company_krs(company_id):
|
||||
db.close()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# REGISTRY ENRICHMENT API ROUTE
|
||||
# ============================================================
|
||||
|
||||
@bp.route('/company/<int:company_id>/enrich-registry', methods=['POST'])
|
||||
@login_required
|
||||
def api_enrich_company_registry(company_id):
|
||||
"""
|
||||
API: Enrich company data from official registries (KRS, CEIDG, Biała Lista VAT).
|
||||
|
||||
Only accessible by administrators.
|
||||
"""
|
||||
if not current_user.is_admin:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Tylko administrator może pobierać dane z rejestrów'
|
||||
}), 403
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
company = db.query(Company).filter_by(id=company_id).first()
|
||||
if not company:
|
||||
return jsonify({'success': False, 'error': 'Firma nie znaleziona'}), 404
|
||||
|
||||
source = None
|
||||
message_parts = []
|
||||
details = {}
|
||||
|
||||
# Strategy 1: Company has KRS — fetch directly
|
||||
if company.krs:
|
||||
from blueprints.admin.routes_membership import _enrich_company_from_krs
|
||||
success = _enrich_company_from_krs(company, db)
|
||||
if success:
|
||||
source = 'KRS'
|
||||
# Count imported data
|
||||
people_count = db.query(CompanyPerson).filter_by(company_id=company.id).count()
|
||||
pkd_count = db.query(CompanyPKD).filter_by(company_id=company.id).count()
|
||||
details = {
|
||||
'people_imported': people_count,
|
||||
'pkd_codes': pkd_count,
|
||||
'legal_form': company.legal_form or ''
|
||||
}
|
||||
message_parts.append(f'zarząd ({people_count} osób)')
|
||||
message_parts.append(f'kody PKD ({pkd_count})')
|
||||
if company.legal_form:
|
||||
message_parts.append(f'forma prawna: {company.legal_form}')
|
||||
else:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Nie udało się pobrać danych z KRS dla numeru {company.krs}'
|
||||
}), 404
|
||||
|
||||
# Strategy 2: No KRS but has NIP — try Biała Lista to find KRS
|
||||
elif company.nip:
|
||||
krs_service = krs_api_service.KRSApiService()
|
||||
biala_lista_result = krs_service.search_by_nip(company.nip)
|
||||
|
||||
if biala_lista_result and biala_lista_result.get('krs'):
|
||||
# Found KRS via Biała Lista — save it and enrich
|
||||
company.krs = biala_lista_result['krs']
|
||||
db.flush()
|
||||
|
||||
from blueprints.admin.routes_membership import _enrich_company_from_krs
|
||||
success = _enrich_company_from_krs(company, db)
|
||||
if success:
|
||||
source = 'KRS (via Biała Lista VAT)'
|
||||
people_count = db.query(CompanyPerson).filter_by(company_id=company.id).count()
|
||||
pkd_count = db.query(CompanyPKD).filter_by(company_id=company.id).count()
|
||||
details = {
|
||||
'krs_found': company.krs,
|
||||
'people_imported': people_count,
|
||||
'pkd_codes': pkd_count,
|
||||
'legal_form': company.legal_form or ''
|
||||
}
|
||||
message_parts.append(f'znaleziono KRS: {company.krs}')
|
||||
message_parts.append(f'zarząd ({people_count} osób)')
|
||||
message_parts.append(f'kody PKD ({pkd_count})')
|
||||
else:
|
||||
source = 'Biała Lista VAT'
|
||||
details = {'krs_found': company.krs}
|
||||
message_parts.append(f'znaleziono KRS: {company.krs} (dane KRS niedostępne)')
|
||||
else:
|
||||
# No KRS found — try CEIDG (likely JDG)
|
||||
ceidg_data = fetch_ceidg_by_nip(company.nip)
|
||||
if ceidg_data:
|
||||
source = 'CEIDG'
|
||||
updated_fields = []
|
||||
|
||||
if ceidg_data.get('nazwa') and not company.legal_name:
|
||||
company.legal_name = ceidg_data['nazwa']
|
||||
updated_fields.append('nazwa pełna')
|
||||
if ceidg_data.get('adres_ulica'):
|
||||
company.address_street = ceidg_data['adres_ulica']
|
||||
updated_fields.append('ulica')
|
||||
if ceidg_data.get('adres_budynek'):
|
||||
company.address_building = ceidg_data['adres_budynek']
|
||||
updated_fields.append('nr budynku')
|
||||
if ceidg_data.get('adres_lokal'):
|
||||
company.address_apartment = ceidg_data['adres_lokal']
|
||||
updated_fields.append('nr lokalu')
|
||||
if ceidg_data.get('adres_kod'):
|
||||
company.address_postal_code = ceidg_data['adres_kod']
|
||||
updated_fields.append('kod pocztowy')
|
||||
if ceidg_data.get('adres_miasto'):
|
||||
company.address_city = ceidg_data['adres_miasto']
|
||||
updated_fields.append('miasto')
|
||||
if ceidg_data.get('email') and not company.email:
|
||||
company.email = ceidg_data['email']
|
||||
updated_fields.append('email')
|
||||
if ceidg_data.get('www') and not company.website:
|
||||
company.website = ceidg_data['www']
|
||||
updated_fields.append('strona www')
|
||||
if ceidg_data.get('telefon') and not company.phone:
|
||||
company.phone = ceidg_data['telefon']
|
||||
updated_fields.append('telefon')
|
||||
if ceidg_data.get('regon') and not company.regon:
|
||||
company.regon = ceidg_data['regon']
|
||||
updated_fields.append('REGON')
|
||||
|
||||
details = {'updated_fields': updated_fields}
|
||||
message_parts.append(f'zaktualizowano {len(updated_fields)} pól')
|
||||
if updated_fields:
|
||||
message_parts.append(', '.join(updated_fields))
|
||||
else:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Nie znaleziono danych w rejestrach KRS ani CEIDG dla tego NIP'
|
||||
}), 404
|
||||
else:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Firma nie ma numeru NIP ani KRS — nie można pobrać danych z rejestrów'
|
||||
}), 400
|
||||
|
||||
db.commit()
|
||||
|
||||
logger.info(f"Registry enrichment for company {company.id} ({company.name}) from {source} by {current_user.email}")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'Pobrano dane z rejestru {source}: {", ".join(message_parts)}',
|
||||
'source': source,
|
||||
'details': details
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Registry enrichment error for company {company_id}: {str(e)}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Błąd podczas pobierania danych: {str(e)}'
|
||||
}), 500
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# AI ENRICHMENT HELPER FUNCTIONS
|
||||
# ============================================================
|
||||
|
||||
@ -241,7 +241,8 @@ def company_detail(company_id):
|
||||
gbp_audit=gbp_audit,
|
||||
it_audit=it_audit,
|
||||
pkd_codes=pkd_codes,
|
||||
can_enrich=can_enrich
|
||||
can_enrich=can_enrich,
|
||||
is_admin=current_user.is_authenticated and current_user.is_admin
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@ -208,6 +208,46 @@
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* Registry Enrichment Button */
|
||||
.registry-enrich-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
padding: var(--spacing-sm) var(--spacing-lg);
|
||||
background: linear-gradient(135deg, #0d9488 0%, #065f46 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: var(--radius);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.registry-enrich-btn:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(13, 148, 136, 0.4);
|
||||
}
|
||||
|
||||
.registry-enrich-btn:disabled {
|
||||
background: #ccc;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.registry-enrich-btn .spinner {
|
||||
display: none;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 2px solid rgba(255,255,255,0.3);
|
||||
border-top-color: white;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
.registry-enrich-btn.loading .spinner { display: inline-block; }
|
||||
.registry-enrich-btn.loading .btn-text { display: none; }
|
||||
|
||||
/* AI Progress Modal */
|
||||
.ai-progress-modal {
|
||||
display: none;
|
||||
@ -597,6 +637,24 @@
|
||||
</svg>
|
||||
<span class="btn-text">Wzbogac dane AI</span>
|
||||
</button>
|
||||
{% if is_admin %}
|
||||
<button
|
||||
id="registryEnrichBtn"
|
||||
class="registry-enrich-btn"
|
||||
data-company-id="{{ company.id }}"
|
||||
data-nip="{{ company.nip or '' }}"
|
||||
data-krs="{{ company.krs or '' }}"
|
||||
>
|
||||
<span class="spinner"></span>
|
||||
<svg class="btn-text" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M4 7V4h16v3"/>
|
||||
<path d="M9 20h6"/>
|
||||
<path d="M12 4v16"/>
|
||||
<rect x="2" y="7" width="20" height="6" rx="1"/>
|
||||
</svg>
|
||||
<span class="btn-text">Pobierz dane urzędowe</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if not can_enrich %}
|
||||
<span style="font-size: var(--font-size-sm); color: var(--text-secondary); margin-left: var(--spacing-sm);">
|
||||
(tylko admin lub wlasciciel)
|
||||
@ -3946,6 +4004,78 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
});
|
||||
|
||||
// Registry enrichment button handler
|
||||
const registryEnrichBtn = document.getElementById('registryEnrichBtn');
|
||||
if (registryEnrichBtn) {
|
||||
registryEnrichBtn.addEventListener('click', async function() {
|
||||
const companyId = this.dataset.companyId;
|
||||
const nip = this.dataset.nip;
|
||||
const krs = this.dataset.krs;
|
||||
|
||||
// Show what will be fetched
|
||||
let sourceInfo = '';
|
||||
if (krs) {
|
||||
sourceInfo = `KRS: ${krs}`;
|
||||
} else if (nip) {
|
||||
sourceInfo = `NIP: ${nip} (szukam w Białej Liście / CEIDG)`;
|
||||
} else {
|
||||
sourceInfo = 'brak NIP i KRS';
|
||||
}
|
||||
|
||||
this.classList.add('loading');
|
||||
this.disabled = true;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/company/${companyId}/enrich-registry`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': '{{ csrf_token() }}'
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
// Show success flash
|
||||
const flash = document.createElement('div');
|
||||
flash.style.cssText = 'position:fixed;top:20px;left:50%;transform:translateX(-50%);background:#065f46;color:white;padding:16px 24px;border-radius:8px;z-index:9999;font-size:14px;max-width:600px;box-shadow:0 4px 12px rgba(0,0,0,0.3);';
|
||||
flash.innerHTML = `<strong>Pobrano dane z ${data.source}</strong><br>${data.message}`;
|
||||
document.body.appendChild(flash);
|
||||
|
||||
setTimeout(() => {
|
||||
flash.remove();
|
||||
window.location.reload();
|
||||
}, 3000);
|
||||
} else {
|
||||
// Show error flash
|
||||
const flash = document.createElement('div');
|
||||
flash.style.cssText = 'position:fixed;top:20px;left:50%;transform:translateX(-50%);background:#991b1b;color:white;padding:16px 24px;border-radius:8px;z-index:9999;font-size:14px;max-width:600px;box-shadow:0 4px 12px rgba(0,0,0,0.3);cursor:pointer;';
|
||||
flash.innerHTML = `<strong>Błąd</strong><br>${data.error}`;
|
||||
flash.onclick = () => flash.remove();
|
||||
document.body.appendChild(flash);
|
||||
|
||||
setTimeout(() => flash.remove(), 5000);
|
||||
|
||||
this.classList.remove('loading');
|
||||
this.disabled = false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Registry enrichment error:', error);
|
||||
const flash = document.createElement('div');
|
||||
flash.style.cssText = 'position:fixed;top:20px;left:50%;transform:translateX(-50%);background:#991b1b;color:white;padding:16px 24px;border-radius:8px;z-index:9999;font-size:14px;max-width:600px;box-shadow:0 4px 12px rgba(0,0,0,0.3);cursor:pointer;';
|
||||
flash.innerHTML = '<strong>Błąd połączenia</strong><br>Nie udało się połączyć z serwerem';
|
||||
flash.onclick = () => flash.remove();
|
||||
document.body.appendChild(flash);
|
||||
|
||||
setTimeout(() => flash.remove(), 5000);
|
||||
|
||||
this.classList.remove('loading');
|
||||
this.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user