refactor: Migrate KRS API routes to admin blueprint
- Created blueprints/admin/routes_krs_api.py with 3 routes: - /admin/krs-api/audit (POST) - /admin/krs-api/audit/batch (POST) - /admin/krs-api/pdf/<company_id> - Updated templates to use new URL paths - Added endpoint aliases for backward compatibility - Removed ~420 lines from app.py (8150 -> 7729) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
236e929d10
commit
e6cca4ec19
429
app.py
429
app.py
@ -6399,432 +6399,11 @@ def _old_admin_krs_audit():
|
||||
db.close()
|
||||
|
||||
|
||||
@app.route('/api/krs/audit', methods=['POST'])
|
||||
@login_required
|
||||
@limiter.limit("200 per hour")
|
||||
def api_krs_audit_trigger():
|
||||
"""
|
||||
API: Trigger KRS audit for a company (admin-only).
|
||||
|
||||
Parses KRS PDF file and extracts all available data:
|
||||
- Basic info (KRS, NIP, REGON, name, legal form)
|
||||
- Capital and shares
|
||||
- Management board, shareholders, procurators
|
||||
- PKD codes
|
||||
- Financial reports
|
||||
|
||||
Request JSON body:
|
||||
- company_id: Company ID (integer)
|
||||
|
||||
Returns:
|
||||
- Success: Audit results saved to database
|
||||
- Error: Error message with status code
|
||||
"""
|
||||
if not current_user.is_admin:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Brak uprawnień. Tylko administrator może uruchamiać audyty KRS.'
|
||||
}), 403
|
||||
|
||||
if not KRS_AUDIT_AVAILABLE:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Usługa audytu KRS jest niedostępna.'
|
||||
}), 503
|
||||
|
||||
data = request.get_json()
|
||||
if not data or not data.get('company_id'):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Podaj company_id firmy do audytu.'
|
||||
}), 400
|
||||
|
||||
company_id = data['company_id']
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
company = db.query(Company).filter_by(id=company_id, status='active').first()
|
||||
if not company:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Firma nie znaleziona.'
|
||||
}), 404
|
||||
|
||||
if not company.krs:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Firma "{company.name}" nie ma numeru KRS.'
|
||||
}), 400
|
||||
|
||||
# Find PDF file
|
||||
pdf_dir = Path('data/krs_pdfs')
|
||||
pdf_files = list(pdf_dir.glob(f'*{company.krs}*.pdf'))
|
||||
|
||||
if not pdf_files:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Nie znaleziono pliku PDF dla KRS {company.krs}. '
|
||||
f'Pobierz odpis z ekrs.ms.gov.pl i umieść w data/krs_pdfs/'
|
||||
}), 404
|
||||
|
||||
pdf_path = pdf_files[0]
|
||||
|
||||
# Create audit record
|
||||
audit = KRSAudit(
|
||||
company_id=company.id,
|
||||
status='parsing',
|
||||
progress_percent=10,
|
||||
progress_message='Parsowanie pliku PDF...',
|
||||
pdf_filename=pdf_path.name,
|
||||
pdf_path=str(pdf_path)
|
||||
)
|
||||
db.add(audit)
|
||||
db.commit()
|
||||
|
||||
# Parse PDF
|
||||
try:
|
||||
parsed_data = parse_krs_pdf(str(pdf_path), verbose=True)
|
||||
|
||||
# Update audit with parsed data
|
||||
audit.status = 'completed'
|
||||
audit.progress_percent = 100
|
||||
audit.progress_message = 'Audyt zakończony pomyślnie'
|
||||
audit.extracted_krs = parsed_data.get('krs')
|
||||
audit.extracted_nazwa = parsed_data.get('nazwa')
|
||||
audit.extracted_nip = parsed_data.get('nip')
|
||||
audit.extracted_regon = parsed_data.get('regon')
|
||||
audit.extracted_forma_prawna = parsed_data.get('forma_prawna')
|
||||
audit.extracted_data_rejestracji = parse_date_str(parsed_data.get('data_rejestracji'))
|
||||
audit.extracted_kapital_zakladowy = parsed_data.get('kapital_zakladowy')
|
||||
audit.extracted_liczba_udzialow = parsed_data.get('liczba_udzialow')
|
||||
audit.extracted_sposob_reprezentacji = parsed_data.get('sposob_reprezentacji')
|
||||
audit.zarzad_count = len(parsed_data.get('zarzad', []))
|
||||
audit.wspolnicy_count = len(parsed_data.get('wspolnicy', []))
|
||||
audit.prokurenci_count = len(parsed_data.get('prokurenci', []))
|
||||
audit.pkd_count = 1 if parsed_data.get('pkd_przewazajacy') else 0
|
||||
audit.pkd_count += len(parsed_data.get('pkd_pozostale', []))
|
||||
|
||||
# Convert non-JSON-serializable values for JSONB storage
|
||||
def make_json_serializable(obj):
|
||||
from decimal import Decimal
|
||||
if isinstance(obj, Decimal):
|
||||
return float(obj)
|
||||
elif isinstance(obj, (datetime, date)):
|
||||
return obj.isoformat()
|
||||
elif isinstance(obj, dict):
|
||||
return {k: make_json_serializable(v) for k, v in obj.items()}
|
||||
elif isinstance(obj, list):
|
||||
return [make_json_serializable(i) for i in obj]
|
||||
return obj
|
||||
|
||||
audit.parsed_data = make_json_serializable(parsed_data)
|
||||
audit.pdf_downloaded_at = datetime.now()
|
||||
|
||||
# Update company with parsed data
|
||||
if parsed_data.get('kapital_zakladowy'):
|
||||
company.capital_amount = parsed_data['kapital_zakladowy']
|
||||
if parsed_data.get('liczba_udzialow'):
|
||||
company.capital_shares_count = parsed_data['liczba_udzialow']
|
||||
if parsed_data.get('wartosc_nominalna_udzialu'):
|
||||
company.capital_share_value = parsed_data['wartosc_nominalna_udzialu']
|
||||
if parsed_data.get('data_rejestracji'):
|
||||
company.krs_registration_date = parse_date_str(parsed_data['data_rejestracji'])
|
||||
if parsed_data.get('sposob_reprezentacji'):
|
||||
company.krs_representation_rules = parsed_data['sposob_reprezentacji']
|
||||
if parsed_data.get('czas_trwania'):
|
||||
company.krs_duration = parsed_data['czas_trwania']
|
||||
company.krs_last_audit_at = datetime.now()
|
||||
company.krs_pdf_path = str(pdf_path)
|
||||
|
||||
# Import PKD codes
|
||||
pkd_main = parsed_data.get('pkd_przewazajacy')
|
||||
if pkd_main:
|
||||
existing = db.query(CompanyPKD).filter_by(
|
||||
company_id=company.id,
|
||||
pkd_code=pkd_main['kod']
|
||||
).first()
|
||||
if not existing:
|
||||
db.add(CompanyPKD(
|
||||
company_id=company.id,
|
||||
pkd_code=pkd_main['kod'],
|
||||
pkd_description=pkd_main['opis'],
|
||||
is_primary=True,
|
||||
source='ekrs'
|
||||
))
|
||||
# Also update Company.pkd_code
|
||||
company.pkd_code = pkd_main['kod']
|
||||
company.pkd_description = pkd_main['opis']
|
||||
|
||||
for pkd in parsed_data.get('pkd_pozostale', []):
|
||||
existing = db.query(CompanyPKD).filter_by(
|
||||
company_id=company.id,
|
||||
pkd_code=pkd['kod']
|
||||
).first()
|
||||
if not existing:
|
||||
db.add(CompanyPKD(
|
||||
company_id=company.id,
|
||||
pkd_code=pkd['kod'],
|
||||
pkd_description=pkd['opis'],
|
||||
is_primary=False,
|
||||
source='ekrs'
|
||||
))
|
||||
|
||||
# Import people (zarząd, wspólnicy)
|
||||
for person_data in parsed_data.get('zarzad', []):
|
||||
_import_krs_person(db, company.id, person_data, 'zarzad', pdf_path.name)
|
||||
|
||||
for person_data in parsed_data.get('wspolnicy', []):
|
||||
_import_krs_person(db, company.id, person_data, 'wspolnik', pdf_path.name)
|
||||
|
||||
for person_data in parsed_data.get('prokurenci', []):
|
||||
_import_krs_person(db, company.id, person_data, 'prokurent', pdf_path.name)
|
||||
|
||||
# Import financial reports
|
||||
for report in parsed_data.get('sprawozdania_finansowe', []):
|
||||
existing = db.query(CompanyFinancialReport).filter_by(
|
||||
company_id=company.id,
|
||||
period_start=parse_date_str(report.get('okres_od')),
|
||||
period_end=parse_date_str(report.get('okres_do'))
|
||||
).first()
|
||||
if not existing:
|
||||
db.add(CompanyFinancialReport(
|
||||
company_id=company.id,
|
||||
period_start=parse_date_str(report.get('okres_od')),
|
||||
period_end=parse_date_str(report.get('okres_do')),
|
||||
filed_at=parse_date_str(report.get('data_zlozenia')),
|
||||
source='ekrs'
|
||||
))
|
||||
|
||||
db.commit()
|
||||
|
||||
logger.info(f"KRS audit completed for {company.name} (KRS: {company.krs})")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'Audyt KRS zakończony dla {company.name}',
|
||||
'company_id': company.id,
|
||||
'data': {
|
||||
'krs': parsed_data.get('krs'),
|
||||
'nazwa': parsed_data.get('nazwa'),
|
||||
'nip': parsed_data.get('nip'),
|
||||
'regon': parsed_data.get('regon'),
|
||||
'kapital': float(parsed_data.get('kapital_zakladowy', 0) or 0),
|
||||
'liczba_udzialow': parsed_data.get('liczba_udzialow'),
|
||||
'zarzad_count': len(parsed_data.get('zarzad', [])),
|
||||
'wspolnicy_count': len(parsed_data.get('wspolnicy', [])),
|
||||
'prokurenci_count': len(parsed_data.get('prokurenci', [])),
|
||||
'pkd_count': audit.pkd_count
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
audit.status = 'error'
|
||||
audit.progress_percent = 0
|
||||
audit.error_message = str(e)
|
||||
db.commit()
|
||||
logger.error(f"KRS audit failed for {company.name}: {e}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Błąd parsowania PDF: {str(e)}'
|
||||
}), 500
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def parse_date_str(date_val):
|
||||
"""Helper to parse date from string or return date object as-is"""
|
||||
if date_val is None:
|
||||
return None
|
||||
if isinstance(date_val, date):
|
||||
return date_val
|
||||
if isinstance(date_val, str):
|
||||
try:
|
||||
return datetime.strptime(date_val, '%Y-%m-%d').date()
|
||||
except:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def _import_krs_person(db, company_id, person_data, role_category, source_document):
|
||||
"""Helper to import a person from KRS data"""
|
||||
pesel = person_data.get('pesel')
|
||||
nazwisko = person_data.get('nazwisko', '')
|
||||
imiona = person_data.get('imiona', '')
|
||||
rola = person_data.get('rola', '')
|
||||
|
||||
# Find or create Person
|
||||
person = None
|
||||
if pesel:
|
||||
person = db.query(Person).filter_by(pesel=pesel).first()
|
||||
|
||||
if not person:
|
||||
# Try to find by name
|
||||
person = db.query(Person).filter_by(
|
||||
nazwisko=nazwisko,
|
||||
imiona=imiona
|
||||
).first()
|
||||
|
||||
if not person:
|
||||
person = Person(
|
||||
pesel=pesel,
|
||||
nazwisko=nazwisko,
|
||||
imiona=imiona
|
||||
)
|
||||
db.add(person)
|
||||
db.flush()
|
||||
|
||||
# Check if relation already exists
|
||||
existing_rel = db.query(CompanyPerson).filter_by(
|
||||
company_id=company_id,
|
||||
person_id=person.id,
|
||||
role_category=role_category
|
||||
).first()
|
||||
|
||||
if not existing_rel:
|
||||
cp = CompanyPerson(
|
||||
company_id=company_id,
|
||||
person_id=person.id,
|
||||
role=rola,
|
||||
role_category=role_category,
|
||||
source='ekrs.ms.gov.pl',
|
||||
source_document=source_document,
|
||||
fetched_at=datetime.now()
|
||||
)
|
||||
# Add shares info for shareholders
|
||||
if role_category == 'wspolnik':
|
||||
cp.shares_count = person_data.get('udzialy_liczba')
|
||||
if person_data.get('udzialy_wartosc'):
|
||||
cp.shares_value = person_data['udzialy_wartosc']
|
||||
if person_data.get('udzialy_procent'):
|
||||
cp.shares_percent = person_data['udzialy_procent']
|
||||
db.add(cp)
|
||||
|
||||
|
||||
@app.route('/api/krs/audit/batch', methods=['POST'])
|
||||
@login_required
|
||||
@limiter.limit("5 per hour")
|
||||
def api_krs_audit_batch():
|
||||
"""
|
||||
API: Trigger batch KRS audit for all companies with KRS numbers.
|
||||
|
||||
This runs audits sequentially to avoid overloading the system.
|
||||
Returns progress updates via the response.
|
||||
"""
|
||||
if not current_user.is_admin:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Brak uprawnień.'
|
||||
}), 403
|
||||
|
||||
if not KRS_AUDIT_AVAILABLE:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Usługa audytu KRS jest niedostępna.'
|
||||
}), 503
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# Get companies with KRS that haven't been audited recently
|
||||
companies = db.query(Company).filter(
|
||||
Company.status == 'active',
|
||||
Company.krs.isnot(None),
|
||||
Company.krs != ''
|
||||
).order_by(Company.name).all()
|
||||
|
||||
results = {
|
||||
'total': len(companies),
|
||||
'success': 0,
|
||||
'failed': 0,
|
||||
'skipped': 0,
|
||||
'details': []
|
||||
}
|
||||
|
||||
pdf_dir = Path('data/krs_pdfs')
|
||||
|
||||
for company in companies:
|
||||
# Find PDF file
|
||||
pdf_files = list(pdf_dir.glob(f'*{company.krs}*.pdf'))
|
||||
|
||||
if not pdf_files:
|
||||
results['skipped'] += 1
|
||||
results['details'].append({
|
||||
'company': company.name,
|
||||
'krs': company.krs,
|
||||
'status': 'skipped',
|
||||
'reason': 'Brak pliku PDF'
|
||||
})
|
||||
continue
|
||||
|
||||
pdf_path = pdf_files[0]
|
||||
|
||||
try:
|
||||
parsed_data = parse_krs_pdf(str(pdf_path))
|
||||
|
||||
# Update company
|
||||
if parsed_data.get('kapital_zakladowy'):
|
||||
company.capital_amount = parsed_data['kapital_zakladowy']
|
||||
if parsed_data.get('liczba_udzialow'):
|
||||
company.capital_shares_count = parsed_data['liczba_udzialow']
|
||||
company.krs_last_audit_at = datetime.now()
|
||||
company.krs_pdf_path = str(pdf_path)
|
||||
|
||||
results['success'] += 1
|
||||
results['details'].append({
|
||||
'company': company.name,
|
||||
'krs': company.krs,
|
||||
'status': 'success'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
results['failed'] += 1
|
||||
results['details'].append({
|
||||
'company': company.name,
|
||||
'krs': company.krs,
|
||||
'status': 'error',
|
||||
'reason': str(e)
|
||||
})
|
||||
|
||||
db.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'Audyt zakończony: {results["success"]} sukces, '
|
||||
f'{results["failed"]} błędów, {results["skipped"]} pominiętych',
|
||||
'results': results
|
||||
})
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@app.route('/api/krs/pdf/<int:company_id>')
|
||||
@login_required
|
||||
def api_krs_pdf_download(company_id):
|
||||
"""
|
||||
API: Download/serve KRS PDF file for a company.
|
||||
"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
company = db.query(Company).filter_by(id=company_id).first()
|
||||
if not company:
|
||||
return jsonify({'error': 'Firma nie znaleziona'}), 404
|
||||
|
||||
if not company.krs_pdf_path:
|
||||
return jsonify({'error': 'Brak pliku PDF'}), 404
|
||||
|
||||
pdf_path = Path(company.krs_pdf_path)
|
||||
if not pdf_path.exists():
|
||||
return jsonify({'error': 'Plik PDF nie istnieje'}), 404
|
||||
|
||||
return send_file(
|
||||
str(pdf_path),
|
||||
mimetype='application/pdf',
|
||||
as_attachment=False,
|
||||
download_name=pdf_path.name
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
# ============================================================
|
||||
# KRS API ROUTES - MOVED TO blueprints/admin/routes_krs_api.py
|
||||
# ============================================================
|
||||
# Routes: /admin/krs-api/audit, /admin/krs-api/audit/batch, /admin/krs-api/pdf/<company_id>
|
||||
|
||||
|
||||
# ============================================================
|
||||
|
||||
@ -326,6 +326,10 @@ def register_blueprints(app):
|
||||
'admin_zopk_fact_duplicates': 'admin.admin_zopk_fact_duplicates',
|
||||
# ZOPK Timeline (Phase 6.3)
|
||||
'admin_zopk_timeline': 'admin.admin_zopk_timeline',
|
||||
# KRS API (Phase 6.4)
|
||||
'api_krs_audit_trigger': 'admin.api_krs_audit_trigger',
|
||||
'api_krs_audit_batch': 'admin.api_krs_audit_batch',
|
||||
'api_krs_pdf_download': 'admin.api_krs_pdf_download',
|
||||
})
|
||||
logger.info("Created admin endpoint aliases")
|
||||
except ImportError as e:
|
||||
|
||||
@ -23,3 +23,4 @@ from . import routes_zopk_news # noqa: E402, F401
|
||||
from . import routes_zopk_knowledge # noqa: E402, F401
|
||||
from . import routes_zopk_timeline # noqa: E402, F401
|
||||
from . import routes_users_api # noqa: E402, F401
|
||||
from . import routes_krs_api # noqa: E402, F401
|
||||
|
||||
475
blueprints/admin/routes_krs_api.py
Normal file
475
blueprints/admin/routes_krs_api.py
Normal file
@ -0,0 +1,475 @@
|
||||
"""
|
||||
KRS API Routes - Admin blueprint
|
||||
|
||||
Migrated from app.py as part of the blueprint refactoring.
|
||||
Contains API routes for KRS (National Court Register) audits.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime, date
|
||||
from pathlib import Path
|
||||
|
||||
from flask import jsonify, request, send_file, current_app
|
||||
from flask_login import current_user, login_required
|
||||
|
||||
from database import (
|
||||
SessionLocal,
|
||||
Company,
|
||||
Person,
|
||||
CompanyPerson,
|
||||
CompanyPKD,
|
||||
CompanyFinancialReport,
|
||||
KRSAudit
|
||||
)
|
||||
from . import bp
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Import limiter from app - will be initialized when app starts
|
||||
def get_limiter():
|
||||
"""Get rate limiter from current app."""
|
||||
return current_app.extensions.get('limiter')
|
||||
|
||||
|
||||
def is_krs_audit_available():
|
||||
"""Check if KRS audit service is available."""
|
||||
try:
|
||||
from krs_audit_service import parse_krs_pdf
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
|
||||
def parse_date_str(date_val):
|
||||
"""Helper to parse date from string or return date object as-is"""
|
||||
if date_val is None:
|
||||
return None
|
||||
if isinstance(date_val, date):
|
||||
return date_val
|
||||
if isinstance(date_val, str):
|
||||
try:
|
||||
return datetime.strptime(date_val, '%Y-%m-%d').date()
|
||||
except Exception:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def _import_krs_person(db, company_id, person_data, role_category, source_document):
|
||||
"""Helper to import a person from KRS data"""
|
||||
pesel = person_data.get('pesel')
|
||||
nazwisko = person_data.get('nazwisko', '')
|
||||
imiona = person_data.get('imiona', '')
|
||||
rola = person_data.get('rola', '')
|
||||
|
||||
# Find or create Person
|
||||
person = None
|
||||
if pesel:
|
||||
person = db.query(Person).filter_by(pesel=pesel).first()
|
||||
|
||||
if not person:
|
||||
# Try to find by name
|
||||
person = db.query(Person).filter_by(
|
||||
nazwisko=nazwisko,
|
||||
imiona=imiona
|
||||
).first()
|
||||
|
||||
if not person:
|
||||
person = Person(
|
||||
pesel=pesel,
|
||||
nazwisko=nazwisko,
|
||||
imiona=imiona
|
||||
)
|
||||
db.add(person)
|
||||
db.flush()
|
||||
|
||||
# Check if relation already exists
|
||||
existing_rel = db.query(CompanyPerson).filter_by(
|
||||
company_id=company_id,
|
||||
person_id=person.id,
|
||||
role_category=role_category
|
||||
).first()
|
||||
|
||||
if not existing_rel:
|
||||
cp = CompanyPerson(
|
||||
company_id=company_id,
|
||||
person_id=person.id,
|
||||
role=rola,
|
||||
role_category=role_category,
|
||||
source='ekrs.ms.gov.pl',
|
||||
source_document=source_document,
|
||||
fetched_at=datetime.now()
|
||||
)
|
||||
# Add shares info for shareholders
|
||||
if role_category == 'wspolnik':
|
||||
cp.shares_count = person_data.get('udzialy_liczba')
|
||||
if person_data.get('udzialy_wartosc'):
|
||||
cp.shares_value = person_data['udzialy_wartosc']
|
||||
if person_data.get('udzialy_procent'):
|
||||
cp.shares_percent = person_data['udzialy_procent']
|
||||
db.add(cp)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# KRS AUDIT API ROUTES
|
||||
# ============================================================
|
||||
|
||||
@bp.route('/krs-api/audit', methods=['POST'])
|
||||
@login_required
|
||||
def api_krs_audit_trigger():
|
||||
"""
|
||||
API: Trigger KRS audit for a company (admin-only).
|
||||
|
||||
Parses KRS PDF file and extracts all available data:
|
||||
- Basic info (KRS, NIP, REGON, name, legal form)
|
||||
- Capital and shares
|
||||
- Management board, shareholders, procurators
|
||||
- PKD codes
|
||||
- Financial reports
|
||||
|
||||
Request JSON body:
|
||||
- company_id: Company ID (integer)
|
||||
|
||||
Returns:
|
||||
- Success: Audit results saved to database
|
||||
- Error: Error message with status code
|
||||
"""
|
||||
if not current_user.is_admin:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Brak uprawnień. Tylko administrator może uruchamiać audyty KRS.'
|
||||
}), 403
|
||||
|
||||
if not is_krs_audit_available():
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Usługa audytu KRS jest niedostępna.'
|
||||
}), 503
|
||||
|
||||
from krs_audit_service import parse_krs_pdf
|
||||
|
||||
data = request.get_json()
|
||||
if not data or not data.get('company_id'):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Podaj company_id firmy do audytu.'
|
||||
}), 400
|
||||
|
||||
company_id = data['company_id']
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
company = db.query(Company).filter_by(id=company_id, status='active').first()
|
||||
if not company:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Firma nie znaleziona.'
|
||||
}), 404
|
||||
|
||||
if not company.krs:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Firma "{company.name}" nie ma numeru KRS.'
|
||||
}), 400
|
||||
|
||||
# Find PDF file
|
||||
pdf_dir = Path('data/krs_pdfs')
|
||||
pdf_files = list(pdf_dir.glob(f'*{company.krs}*.pdf'))
|
||||
|
||||
if not pdf_files:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Nie znaleziono pliku PDF dla KRS {company.krs}. '
|
||||
f'Pobierz odpis z ekrs.ms.gov.pl i umieść w data/krs_pdfs/'
|
||||
}), 404
|
||||
|
||||
pdf_path = pdf_files[0]
|
||||
|
||||
# Create audit record
|
||||
audit = KRSAudit(
|
||||
company_id=company.id,
|
||||
status='parsing',
|
||||
progress_percent=10,
|
||||
progress_message='Parsowanie pliku PDF...',
|
||||
pdf_filename=pdf_path.name,
|
||||
pdf_path=str(pdf_path)
|
||||
)
|
||||
db.add(audit)
|
||||
db.commit()
|
||||
|
||||
# Parse PDF
|
||||
try:
|
||||
parsed_data = parse_krs_pdf(str(pdf_path), verbose=True)
|
||||
|
||||
# Update audit with parsed data
|
||||
audit.status = 'completed'
|
||||
audit.progress_percent = 100
|
||||
audit.progress_message = 'Audyt zakończony pomyślnie'
|
||||
audit.extracted_krs = parsed_data.get('krs')
|
||||
audit.extracted_nazwa = parsed_data.get('nazwa')
|
||||
audit.extracted_nip = parsed_data.get('nip')
|
||||
audit.extracted_regon = parsed_data.get('regon')
|
||||
audit.extracted_forma_prawna = parsed_data.get('forma_prawna')
|
||||
audit.extracted_data_rejestracji = parse_date_str(parsed_data.get('data_rejestracji'))
|
||||
audit.extracted_kapital_zakladowy = parsed_data.get('kapital_zakladowy')
|
||||
audit.extracted_liczba_udzialow = parsed_data.get('liczba_udzialow')
|
||||
audit.extracted_sposob_reprezentacji = parsed_data.get('sposob_reprezentacji')
|
||||
audit.zarzad_count = len(parsed_data.get('zarzad', []))
|
||||
audit.wspolnicy_count = len(parsed_data.get('wspolnicy', []))
|
||||
audit.prokurenci_count = len(parsed_data.get('prokurenci', []))
|
||||
audit.pkd_count = 1 if parsed_data.get('pkd_przewazajacy') else 0
|
||||
audit.pkd_count += len(parsed_data.get('pkd_pozostale', []))
|
||||
|
||||
# Convert non-JSON-serializable values for JSONB storage
|
||||
def make_json_serializable(obj):
|
||||
from decimal import Decimal
|
||||
if isinstance(obj, Decimal):
|
||||
return float(obj)
|
||||
elif isinstance(obj, (datetime, date)):
|
||||
return obj.isoformat()
|
||||
elif isinstance(obj, dict):
|
||||
return {k: make_json_serializable(v) for k, v in obj.items()}
|
||||
elif isinstance(obj, list):
|
||||
return [make_json_serializable(i) for i in obj]
|
||||
return obj
|
||||
|
||||
audit.parsed_data = make_json_serializable(parsed_data)
|
||||
audit.pdf_downloaded_at = datetime.now()
|
||||
|
||||
# Update company with parsed data
|
||||
if parsed_data.get('kapital_zakladowy'):
|
||||
company.capital_amount = parsed_data['kapital_zakladowy']
|
||||
if parsed_data.get('liczba_udzialow'):
|
||||
company.capital_shares_count = parsed_data['liczba_udzialow']
|
||||
if parsed_data.get('wartosc_nominalna_udzialu'):
|
||||
company.capital_share_value = parsed_data['wartosc_nominalna_udzialu']
|
||||
if parsed_data.get('data_rejestracji'):
|
||||
company.krs_registration_date = parse_date_str(parsed_data['data_rejestracji'])
|
||||
if parsed_data.get('sposob_reprezentacji'):
|
||||
company.krs_representation_rules = parsed_data['sposob_reprezentacji']
|
||||
if parsed_data.get('czas_trwania'):
|
||||
company.krs_duration = parsed_data['czas_trwania']
|
||||
company.krs_last_audit_at = datetime.now()
|
||||
company.krs_pdf_path = str(pdf_path)
|
||||
|
||||
# Import PKD codes
|
||||
pkd_main = parsed_data.get('pkd_przewazajacy')
|
||||
if pkd_main:
|
||||
existing = db.query(CompanyPKD).filter_by(
|
||||
company_id=company.id,
|
||||
pkd_code=pkd_main['kod']
|
||||
).first()
|
||||
if not existing:
|
||||
db.add(CompanyPKD(
|
||||
company_id=company.id,
|
||||
pkd_code=pkd_main['kod'],
|
||||
pkd_description=pkd_main['opis'],
|
||||
is_primary=True,
|
||||
source='ekrs'
|
||||
))
|
||||
# Also update Company.pkd_code
|
||||
company.pkd_code = pkd_main['kod']
|
||||
company.pkd_description = pkd_main['opis']
|
||||
|
||||
for pkd in parsed_data.get('pkd_pozostale', []):
|
||||
existing = db.query(CompanyPKD).filter_by(
|
||||
company_id=company.id,
|
||||
pkd_code=pkd['kod']
|
||||
).first()
|
||||
if not existing:
|
||||
db.add(CompanyPKD(
|
||||
company_id=company.id,
|
||||
pkd_code=pkd['kod'],
|
||||
pkd_description=pkd['opis'],
|
||||
is_primary=False,
|
||||
source='ekrs'
|
||||
))
|
||||
|
||||
# Import people (zarząd, wspólnicy)
|
||||
for person_data in parsed_data.get('zarzad', []):
|
||||
_import_krs_person(db, company.id, person_data, 'zarzad', pdf_path.name)
|
||||
|
||||
for person_data in parsed_data.get('wspolnicy', []):
|
||||
_import_krs_person(db, company.id, person_data, 'wspolnik', pdf_path.name)
|
||||
|
||||
for person_data in parsed_data.get('prokurenci', []):
|
||||
_import_krs_person(db, company.id, person_data, 'prokurent', pdf_path.name)
|
||||
|
||||
# Import financial reports
|
||||
for report in parsed_data.get('sprawozdania_finansowe', []):
|
||||
existing = db.query(CompanyFinancialReport).filter_by(
|
||||
company_id=company.id,
|
||||
period_start=parse_date_str(report.get('okres_od')),
|
||||
period_end=parse_date_str(report.get('okres_do'))
|
||||
).first()
|
||||
if not existing:
|
||||
db.add(CompanyFinancialReport(
|
||||
company_id=company.id,
|
||||
period_start=parse_date_str(report.get('okres_od')),
|
||||
period_end=parse_date_str(report.get('okres_do')),
|
||||
filed_at=parse_date_str(report.get('data_zlozenia')),
|
||||
source='ekrs'
|
||||
))
|
||||
|
||||
db.commit()
|
||||
|
||||
logger.info(f"KRS audit completed for {company.name} (KRS: {company.krs})")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'Audyt KRS zakończony dla {company.name}',
|
||||
'company_id': company.id,
|
||||
'data': {
|
||||
'krs': parsed_data.get('krs'),
|
||||
'nazwa': parsed_data.get('nazwa'),
|
||||
'nip': parsed_data.get('nip'),
|
||||
'regon': parsed_data.get('regon'),
|
||||
'kapital': float(parsed_data.get('kapital_zakladowy', 0) or 0),
|
||||
'liczba_udzialow': parsed_data.get('liczba_udzialow'),
|
||||
'zarzad_count': len(parsed_data.get('zarzad', [])),
|
||||
'wspolnicy_count': len(parsed_data.get('wspolnicy', [])),
|
||||
'prokurenci_count': len(parsed_data.get('prokurenci', [])),
|
||||
'pkd_count': audit.pkd_count
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
audit.status = 'error'
|
||||
audit.progress_percent = 0
|
||||
audit.error_message = str(e)
|
||||
db.commit()
|
||||
logger.error(f"KRS audit failed for {company.name}: {e}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Błąd parsowania PDF: {str(e)}'
|
||||
}), 500
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@bp.route('/krs-api/audit/batch', methods=['POST'])
|
||||
@login_required
|
||||
def api_krs_audit_batch():
|
||||
"""
|
||||
API: Trigger batch KRS audit for all companies with KRS numbers.
|
||||
|
||||
This runs audits sequentially to avoid overloading the system.
|
||||
Returns progress updates via the response.
|
||||
"""
|
||||
if not current_user.is_admin:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Brak uprawnień.'
|
||||
}), 403
|
||||
|
||||
if not is_krs_audit_available():
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Usługa audytu KRS jest niedostępna.'
|
||||
}), 503
|
||||
|
||||
from krs_audit_service import parse_krs_pdf
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# Get companies with KRS that haven't been audited recently
|
||||
companies = db.query(Company).filter(
|
||||
Company.status == 'active',
|
||||
Company.krs.isnot(None),
|
||||
Company.krs != ''
|
||||
).order_by(Company.name).all()
|
||||
|
||||
results = {
|
||||
'total': len(companies),
|
||||
'success': 0,
|
||||
'failed': 0,
|
||||
'skipped': 0,
|
||||
'details': []
|
||||
}
|
||||
|
||||
pdf_dir = Path('data/krs_pdfs')
|
||||
|
||||
for company in companies:
|
||||
# Find PDF file
|
||||
pdf_files = list(pdf_dir.glob(f'*{company.krs}*.pdf'))
|
||||
|
||||
if not pdf_files:
|
||||
results['skipped'] += 1
|
||||
results['details'].append({
|
||||
'company': company.name,
|
||||
'krs': company.krs,
|
||||
'status': 'skipped',
|
||||
'reason': 'Brak pliku PDF'
|
||||
})
|
||||
continue
|
||||
|
||||
pdf_path = pdf_files[0]
|
||||
|
||||
try:
|
||||
parsed_data = parse_krs_pdf(str(pdf_path))
|
||||
|
||||
# Update company
|
||||
if parsed_data.get('kapital_zakladowy'):
|
||||
company.capital_amount = parsed_data['kapital_zakladowy']
|
||||
if parsed_data.get('liczba_udzialow'):
|
||||
company.capital_shares_count = parsed_data['liczba_udzialow']
|
||||
company.krs_last_audit_at = datetime.now()
|
||||
company.krs_pdf_path = str(pdf_path)
|
||||
|
||||
results['success'] += 1
|
||||
results['details'].append({
|
||||
'company': company.name,
|
||||
'krs': company.krs,
|
||||
'status': 'success'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
results['failed'] += 1
|
||||
results['details'].append({
|
||||
'company': company.name,
|
||||
'krs': company.krs,
|
||||
'status': 'error',
|
||||
'reason': str(e)
|
||||
})
|
||||
|
||||
db.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'Audyt zakończony: {results["success"]} sukces, '
|
||||
f'{results["failed"]} błędów, {results["skipped"]} pominiętych',
|
||||
'results': results
|
||||
})
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@bp.route('/krs-api/pdf/<int:company_id>')
|
||||
@login_required
|
||||
def api_krs_pdf_download(company_id):
|
||||
"""
|
||||
API: Download/serve KRS PDF file for a company.
|
||||
"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
company = db.query(Company).filter_by(id=company_id).first()
|
||||
if not company:
|
||||
return jsonify({'error': 'Firma nie znaleziona'}), 404
|
||||
|
||||
if not company.krs_pdf_path:
|
||||
return jsonify({'error': 'Brak pliku PDF'}), 404
|
||||
|
||||
pdf_path = Path(company.krs_pdf_path)
|
||||
if not pdf_path.exists():
|
||||
return jsonify({'error': 'Plik PDF nie istnieje'}), 404
|
||||
|
||||
return send_file(
|
||||
str(pdf_path),
|
||||
mimetype='application/pdf',
|
||||
as_attachment=False,
|
||||
download_name=pdf_path.name
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
@ -1055,7 +1055,7 @@ async function runSingleAudit(companyId, companyName) {
|
||||
addLogEntry(progressLog, 'Ekstrakcja danych z PDF...', 'info');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/krs/audit', {
|
||||
const response = await fetch('/admin/krs-api/audit', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -1203,7 +1203,7 @@ async function runBatchAudit() {
|
||||
progressMessage.textContent = `[${i + 1}/${companies.length}] Przetwarzanie: ${company.name}...`;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/krs/audit', {
|
||||
const response = await fetch('/admin/krs-api/audit', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@ -1279,7 +1279,7 @@
|
||||
</div>
|
||||
<div style="margin-left: auto; display: flex; gap: var(--spacing-sm); flex-wrap: wrap;">
|
||||
{% if company.krs_pdf_path %}
|
||||
<a href="/api/krs/pdf/{{ company.id }}" target="_blank" rel="noopener noreferrer"
|
||||
<a href="/admin/krs-api/pdf/{{ company.id }}" target="_blank" rel="noopener noreferrer"
|
||||
style="padding: 8px 16px; background: #ef4444; color: white; border-radius: var(--radius); text-decoration: none; font-size: var(--font-size-sm); font-weight: 600; white-space: nowrap; display: inline-flex; align-items: center; gap: 6px;"
|
||||
title="Pobierz odpis pełny KRS w formacie PDF">
|
||||
<svg width="16" height="16" fill="currentColor" viewBox="0 0 24 24"><path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20M10,13H7V11H10V13M14,13H11V11H14V13M10,16H7V14H10V16M14,16H11V14H14V16Z"/></svg>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user