feat: Display ALL KRS data in company profile
- Add krs_raw_data, krs_fetched_at, krs_registration_date, krs_representation, krs_activities columns to Company model - Save complete KRS API response for full data access - Display in company profile: - Board members (zarząd) with functions and avatars - Shareholders (wspólnicy) with share amounts - Representation method (sposób reprezentacji) - Business activities (PKD codes) - Registration date with years active - KRS address with region info - OPP (public benefit) status - Metadata (stan_z_dnia, data_odpisu) - Add migration 037_krs_extended_data.sql Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
a73117ad4a
commit
52061fa949
@ -693,6 +693,13 @@ class Company(Base):
|
||||
ceidg_raw_data = Column(PG_JSONB)
|
||||
ceidg_fetched_at = Column(DateTime)
|
||||
|
||||
# === KRS DATA (API prs.ms.gov.pl) ===
|
||||
krs_raw_data = Column(PG_JSONB) # Pełna odpowiedź z KRS API
|
||||
krs_fetched_at = Column(DateTime)
|
||||
krs_registration_date = Column(Date) # Data rejestracji w KRS
|
||||
krs_representation = Column(Text) # Sposób reprezentacji spółki
|
||||
krs_activities = Column(PG_JSONB, default=[]) # Przedmiot działalności
|
||||
|
||||
# Data source tracking
|
||||
data_source = Column(String(100))
|
||||
data_quality_score = Column(Integer)
|
||||
|
||||
29
database/migrations/037_krs_extended_data.sql
Normal file
29
database/migrations/037_krs_extended_data.sql
Normal file
@ -0,0 +1,29 @@
|
||||
-- ============================================================
|
||||
-- 037_krs_extended_data.sql
|
||||
-- Rozszerzone dane z KRS API
|
||||
-- ============================================================
|
||||
|
||||
-- Surowe dane z KRS API (JSONB)
|
||||
ALTER TABLE companies ADD COLUMN IF NOT EXISTS krs_raw_data JSONB;
|
||||
|
||||
-- Timestamp ostatniego pobrania z KRS
|
||||
ALTER TABLE companies ADD COLUMN IF NOT EXISTS krs_fetched_at TIMESTAMP;
|
||||
|
||||
-- Data rejestracji w KRS
|
||||
ALTER TABLE companies ADD COLUMN IF NOT EXISTS krs_registration_date DATE;
|
||||
|
||||
-- Sposób reprezentacji
|
||||
ALTER TABLE companies ADD COLUMN IF NOT EXISTS krs_representation TEXT;
|
||||
|
||||
-- Przedmiot działalności z KRS (JSONB array)
|
||||
ALTER TABLE companies ADD COLUMN IF NOT EXISTS krs_activities JSONB DEFAULT '[]';
|
||||
|
||||
-- Komentarze
|
||||
COMMENT ON COLUMN companies.krs_raw_data IS 'Pełna odpowiedź z API KRS (JSON)';
|
||||
COMMENT ON COLUMN companies.krs_fetched_at IS 'Data ostatniego pobrania danych z KRS';
|
||||
COMMENT ON COLUMN companies.krs_registration_date IS 'Data rejestracji w KRS';
|
||||
COMMENT ON COLUMN companies.krs_representation IS 'Sposób reprezentacji spółki';
|
||||
COMMENT ON COLUMN companies.krs_activities IS 'Przedmiot działalności z KRS jako JSON array';
|
||||
|
||||
-- Grant permissions
|
||||
GRANT ALL ON TABLE companies TO nordabiz_app;
|
||||
@ -495,7 +495,7 @@ def update_company_from_ceidg(company_id: int, ceidg_data: dict, db) -> bool:
|
||||
|
||||
def update_company_from_krs(company_id: int, krs_data: dict, db) -> bool:
|
||||
"""
|
||||
Aktualizuje firmę w bazie danymi z KRS API.
|
||||
Aktualizuje firmę w bazie WSZYSTKIMI danymi z KRS API.
|
||||
|
||||
Args:
|
||||
company_id: ID firmy w naszej bazie
|
||||
@ -518,7 +518,7 @@ def update_company_from_krs(company_id: int, krs_data: dict, db) -> bool:
|
||||
if krs_data.get("nip") and not company.nip:
|
||||
company.nip = krs_data.get("nip")
|
||||
if krs_data.get("regon") and not company.regon:
|
||||
company.regon = krs_data.get("regon")
|
||||
company.regon = krs_data.get("regon")[:14] if krs_data.get("regon") else None
|
||||
|
||||
# Forma prawna
|
||||
if krs_data.get("forma_prawna"):
|
||||
@ -549,16 +549,32 @@ def update_company_from_krs(company_id: int, krs_data: dict, db) -> bool:
|
||||
if kapital.get("zakladowy"):
|
||||
company.capital_amount = kapital.get("zakladowy")
|
||||
|
||||
# Data rejestracji
|
||||
# Data rejestracji w KRS
|
||||
daty = krs_data.get("daty", {})
|
||||
if daty.get("rejestracji"):
|
||||
from datetime import datetime as dt
|
||||
try:
|
||||
company.business_start_date = dt.strptime(
|
||||
daty.get("rejestracji"), "%Y-%m-%d"
|
||||
# Format: "10.02.2021"
|
||||
company.krs_registration_date = dt.strptime(
|
||||
daty.get("rejestracji"), "%d.%m.%Y"
|
||||
).date()
|
||||
except:
|
||||
pass
|
||||
# Też ustaw business_start_date jeśli puste
|
||||
if not company.business_start_date:
|
||||
company.business_start_date = company.krs_registration_date
|
||||
except Exception as e:
|
||||
print(f" [WARN] Nie można sparsować daty: {daty.get('rejestracji')} - {e}")
|
||||
|
||||
# Sposób reprezentacji
|
||||
if krs_data.get("sposob_reprezentacji"):
|
||||
company.krs_representation = krs_data.get("sposob_reprezentacji")
|
||||
|
||||
# Przedmiot działalności (PKD z KRS)
|
||||
if krs_data.get("przedmiot_dzialalnosci"):
|
||||
company.krs_activities = krs_data.get("przedmiot_dzialalnosci")
|
||||
|
||||
# SUROWE DANE - zapisz całą odpowiedź z API
|
||||
company.krs_raw_data = krs_data
|
||||
company.krs_fetched_at = datetime.now()
|
||||
|
||||
# Data source
|
||||
company.data_source = "KRS API"
|
||||
@ -568,6 +584,8 @@ def update_company_from_krs(company_id: int, krs_data: dict, db) -> bool:
|
||||
|
||||
except Exception as e:
|
||||
print(f" [ERROR] Błąd aktualizacji: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
|
||||
@ -1185,6 +1185,8 @@
|
||||
<div style="font-size: var(--font-size-xs); color: #15803d; margin-top: 2px;">
|
||||
{% if company.ceidg_fetched_at %}
|
||||
Pobrano: {{ company.ceidg_fetched_at.strftime('%d.%m.%Y %H:%M') }} •
|
||||
{% elif company.krs_fetched_at %}
|
||||
Pobrano: {{ company.krs_fetched_at.strftime('%d.%m.%Y %H:%M') }} •
|
||||
{% elif company.last_verified_at %}
|
||||
Zweryfikowano: {{ company.last_verified_at.strftime('%d.%m.%Y %H:%M') }} •
|
||||
{% endif %}
|
||||
@ -1336,6 +1338,7 @@
|
||||
|
||||
<!-- ===== DANE KRS ===== -->
|
||||
{% if company.krs and company.data_source == 'KRS API' and not company.ceidg_id %}
|
||||
{% set krs = company.krs_raw_data or {} %}
|
||||
|
||||
<!-- Forma prawna -->
|
||||
{% if company.legal_form %}
|
||||
@ -1354,6 +1357,9 @@
|
||||
<div style="font-size: var(--font-size-xl); font-weight: 700; color: #22c55e; margin-top: 4px;">
|
||||
{{ '{:,.2f}'.format(company.capital_amount).replace(',', ' ') }} PLN
|
||||
</div>
|
||||
{% if krs.kapital and krs.kapital.waluta %}
|
||||
<div style="font-size: var(--font-size-xs); color: var(--text-muted); margin-top: 4px;">Waluta: {{ krs.kapital.waluta }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@ -1365,6 +1371,170 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Data rejestracji w KRS -->
|
||||
{% if company.krs_registration_date or (krs.daty and krs.daty.rejestracji) %}
|
||||
<div style="background: var(--background); border-radius: var(--radius-lg); padding: var(--spacing-lg); border-left: 4px solid #059669;">
|
||||
<div style="font-size: var(--font-size-sm); color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.05em;">Data rejestracji w KRS</div>
|
||||
<div style="font-size: var(--font-size-xl); font-weight: 700; color: #059669; margin-top: 4px;">
|
||||
{% if company.krs_registration_date %}
|
||||
{{ company.krs_registration_date.strftime('%d.%m.%Y') }}
|
||||
{% else %}
|
||||
{{ krs.daty.rejestracji }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if company.krs_registration_date %}
|
||||
{% set years_active = ((now.date() - company.krs_registration_date).days / 365.25)|int %}
|
||||
{% if years_active > 0 %}
|
||||
<div style="font-size: var(--font-size-xs); color: var(--text-muted); margin-top: 4px;">{{ years_active }} lat działalności</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Sposób reprezentacji -->
|
||||
{% if company.krs_representation or krs.sposob_reprezentacji %}
|
||||
<div style="background: var(--background); border-radius: var(--radius-lg); padding: var(--spacing-lg); border-left: 4px solid #8b5cf6; grid-column: span 2;">
|
||||
<div style="font-size: var(--font-size-sm); color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: var(--spacing-sm);">Sposób reprezentacji</div>
|
||||
<div style="font-size: var(--font-size-base); color: var(--text-primary); line-height: 1.6;">
|
||||
{{ company.krs_representation or krs.sposob_reprezentacji }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Zarząd -->
|
||||
{% if krs.zarzad and krs.zarzad|length > 0 %}
|
||||
<div style="background: var(--background); border-radius: var(--radius-lg); padding: var(--spacing-lg); border-left: 4px solid #0ea5e9; grid-column: span 2;">
|
||||
<div style="font-size: var(--font-size-sm); color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: var(--spacing-md);">
|
||||
Zarząd ({{ krs.zarzad|length }} {{ 'osoba' if krs.zarzad|length == 1 else 'osoby' if krs.zarzad|length < 5 else 'osób' }})
|
||||
</div>
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--spacing-md);">
|
||||
{% for osoba in krs.zarzad %}
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-sm); padding: var(--spacing-sm); background: var(--surface); border-radius: var(--radius);">
|
||||
<div style="width: 40px; height: 40px; border-radius: 50%; background: linear-gradient(135deg, #0ea5e9 0%, #0284c7 100%); display: flex; align-items: center; justify-content: center; color: white; font-weight: 700; font-size: var(--font-size-sm);">
|
||||
{{ osoba.imie[0] if osoba.imie else '?' }}{{ osoba.nazwisko[0] if osoba.nazwisko else '' }}
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-weight: 600; color: var(--text-primary);">
|
||||
{{ osoba.imie }} {% if osoba.imie_drugie %}{{ osoba.imie_drugie }} {% endif %}{{ osoba.nazwisko }}
|
||||
</div>
|
||||
{% if osoba.funkcja %}
|
||||
<div style="font-size: var(--font-size-xs); color: #0ea5e9; font-weight: 500;">{{ osoba.funkcja }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Wspólnicy -->
|
||||
{% if krs.wspolnicy and krs.wspolnicy|length > 0 %}
|
||||
<div style="background: var(--background); border-radius: var(--radius-lg); padding: var(--spacing-lg); border-left: 4px solid #f59e0b; grid-column: span 2;">
|
||||
<div style="font-size: var(--font-size-sm); color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: var(--spacing-md);">
|
||||
Wspólnicy ({{ krs.wspolnicy|length }})
|
||||
</div>
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: var(--spacing-md);">
|
||||
{% for wspolnik in krs.wspolnicy %}
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-sm); padding: var(--spacing-sm); background: var(--surface); border-radius: var(--radius);">
|
||||
<div style="width: 40px; height: 40px; border-radius: 50%; background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); display: flex; align-items: center; justify-content: center; color: white; font-weight: 700; font-size: var(--font-size-sm);">
|
||||
{{ wspolnik.imie[0] if wspolnik.imie else '?' }}{{ wspolnik.nazwisko[0] if wspolnik.nazwisko else '' }}
|
||||
</div>
|
||||
<div style="flex: 1;">
|
||||
<div style="font-weight: 600; color: var(--text-primary);">
|
||||
{{ wspolnik.imie }} {{ wspolnik.nazwisko }}
|
||||
</div>
|
||||
{% if wspolnik.udzialy %}
|
||||
<div style="font-size: var(--font-size-xs); color: var(--text-muted);">
|
||||
Udziały: {{ wspolnik.udzialy }}
|
||||
{% if wspolnik.calosc_udzialow %}<span style="color: #f59e0b; font-weight: 600;">(100%)</span>{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Przedmiot działalności (PKD) -->
|
||||
{% if company.krs_activities and company.krs_activities|length > 0 %}
|
||||
<div style="background: var(--background); border-radius: var(--radius-lg); padding: var(--spacing-lg); border-left: 4px solid #a855f7; grid-column: span 2;">
|
||||
<div style="font-size: var(--font-size-sm); color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: var(--spacing-md);">
|
||||
Przedmiot działalności z KRS ({{ company.krs_activities|length }} PKD)
|
||||
</div>
|
||||
<div style="display: flex; flex-wrap: wrap; gap: var(--spacing-sm); max-height: 200px; overflow-y: auto;">
|
||||
{% for pkd in company.krs_activities %}
|
||||
<span style="background: var(--surface); color: var(--text-secondary); padding: 4px 10px; border-radius: var(--radius); font-size: var(--font-size-sm); border: 1px solid var(--border);">
|
||||
{{ pkd }}
|
||||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% elif krs.przedmiot_dzialalnosci and krs.przedmiot_dzialalnosci|length > 0 %}
|
||||
<div style="background: var(--background); border-radius: var(--radius-lg); padding: var(--spacing-lg); border-left: 4px solid #a855f7; grid-column: span 2;">
|
||||
<div style="font-size: var(--font-size-sm); color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: var(--spacing-md);">
|
||||
Przedmiot działalności z KRS ({{ krs.przedmiot_dzialalnosci|length }} PKD)
|
||||
</div>
|
||||
<div style="display: flex; flex-wrap: wrap; gap: var(--spacing-sm); max-height: 200px; overflow-y: auto;">
|
||||
{% for pkd in krs.przedmiot_dzialalnosci %}
|
||||
<span style="background: var(--surface); color: var(--text-secondary); padding: 4px 10px; border-radius: var(--radius); font-size: var(--font-size-sm); border: 1px solid var(--border);">
|
||||
{{ pkd }}
|
||||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Adres z KRS -->
|
||||
{% if krs.adres and (krs.adres.ulica or krs.adres.miejscowosc) %}
|
||||
<div style="background: var(--background); border-radius: var(--radius-lg); padding: var(--spacing-lg); border-left: 4px solid #64748b;">
|
||||
<div style="font-size: var(--font-size-sm); color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: var(--spacing-sm);">Adres siedziby z KRS</div>
|
||||
<div style="font-size: var(--font-size-base); color: var(--text-primary);">
|
||||
{% if krs.adres.ulica %}{{ krs.adres.ulica }}{% endif %}
|
||||
{% if krs.adres.nr_domu %} {{ krs.adres.nr_domu }}{% endif %}
|
||||
{% if krs.adres.nr_lokalu %}/{{ krs.adres.nr_lokalu }}{% endif %}<br>
|
||||
{% if krs.adres.kod_pocztowy %}{{ krs.adres.kod_pocztowy }} {% endif %}{{ krs.adres.miejscowosc or '' }}
|
||||
</div>
|
||||
{% if krs.adres.powiat or krs.adres.wojewodztwo %}
|
||||
<div style="font-size: var(--font-size-xs); color: var(--text-muted); margin-top: 4px;">
|
||||
{% if krs.adres.powiat %}pow. {{ krs.adres.powiat }}, {% endif %}woj. {{ krs.adres.wojewodztwo or '' }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- OPP Status -->
|
||||
{% if krs.czy_opp %}
|
||||
<div style="background: var(--background); border-radius: var(--radius-lg); padding: var(--spacing-lg); border-left: 4px solid #ec4899;">
|
||||
<div style="font-size: var(--font-size-sm); color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.05em;">Status OPP</div>
|
||||
<div style="font-size: var(--font-size-lg); font-weight: 700; color: #ec4899; margin-top: 4px;">
|
||||
Organizacja Pożytku Publicznego
|
||||
</div>
|
||||
<div style="font-size: var(--font-size-xs); color: var(--text-muted); margin-top: 4px;">Można przekazać 1,5% podatku</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Metadane KRS -->
|
||||
{% if krs.metadata or krs.daty %}
|
||||
<div style="background: var(--background); border-radius: var(--radius-lg); padding: var(--spacing-lg); border-left: 4px solid #94a3b8;">
|
||||
<div style="font-size: var(--font-size-sm); color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: var(--spacing-sm);">Informacje o odpisie KRS</div>
|
||||
<div style="font-size: var(--font-size-sm); color: var(--text-muted);">
|
||||
{% if krs.metadata %}
|
||||
{% if krs.metadata.stan_z_dnia %}<div>Stan na dzień: <strong>{{ krs.metadata.stan_z_dnia }}</strong></div>{% endif %}
|
||||
{% if krs.metadata.data_odpisu %}<div>Data odpisu: <strong>{{ krs.metadata.data_odpisu }}</strong></div>{% endif %}
|
||||
{% endif %}
|
||||
{% if krs.daty %}
|
||||
{% if krs.daty.ostatniego_wpisu %}<div>Ostatni wpis: <strong>{{ krs.daty.ostatniego_wpisu }}</strong>{% if krs.daty.numer_ostatniego_wpisu %} (nr {{ krs.daty.numer_ostatniego_wpisu }}){% endif %}</div>{% endif %}
|
||||
{% endif %}
|
||||
{% if company.krs_fetched_at %}
|
||||
<div style="margin-top: var(--spacing-sm); padding-top: var(--spacing-sm); border-top: 1px solid var(--border);">
|
||||
Pobrano z API: <strong>{{ company.krs_fetched_at.strftime('%d.%m.%Y %H:%M') }}</strong>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
<!-- ===== KONIEC DANE KRS ===== -->
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user