feat: Link Users to Persons (KRS data)

- Add person_id column to users table
- Template shows person profile link when person_id exists
- Add script to match and link users to persons by name

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-01-13 15:07:02 +01:00
parent 1207a188f5
commit f174f4d4da
3 changed files with 100 additions and 5 deletions

View File

@ -182,6 +182,8 @@ class User(Base, UserMixin):
company_nip = Column(String(10))
company_id = Column(Integer, ForeignKey('companies.id'), nullable=True)
company = relationship('Company', backref='users', lazy='joined') # eager load to avoid DetachedInstanceError
person_id = Column(Integer, ForeignKey('people.id'), nullable=True)
person = relationship('Person', backref='users', lazy='joined') # Link to Person (KRS data)
phone = Column(String(50))
# Status

View File

@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""
Link Users to Persons based on name matching.
Adds person_id column to users table and links users to their Person records.
"""
import os
import sys
from dotenv import load_dotenv
# Load .env first
load_dotenv()
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from sqlalchemy import text
from database import SessionLocal, User, Person
def main():
db = SessionLocal()
# Step 1: Add person_id column if not exists
try:
db.execute(text("""
ALTER TABLE users
ADD COLUMN IF NOT EXISTS person_id INTEGER REFERENCES people(id)
"""))
db.commit()
print("✓ Kolumna person_id dodana (lub już istnieje)")
except Exception as e:
print(f"Uwaga przy dodawaniu kolumny: {e}")
db.rollback()
# Step 2: Get all users with names
users = db.query(User).filter(User.name.isnot(None)).all()
print(f"\nZnaleziono {len(users)} użytkowników z nazwami")
# Step 3: Get all persons
persons = db.query(Person).all()
print(f"Znaleziono {len(persons)} osób w bazie KRS")
# Build a lookup dict for persons by full name (lowercase)
person_lookup = {}
for p in persons:
full_name = f"{p.imiona} {p.nazwisko}".lower().strip()
person_lookup[full_name] = p
# Also try just first name + last name (first word of imiona)
first_name = p.imiona.split()[0] if p.imiona else ""
short_name = f"{first_name} {p.nazwisko}".lower().strip()
if short_name not in person_lookup:
person_lookup[short_name] = p
# Step 4: Match users to persons
matched = 0
for user in users:
if user.person_id:
print(f" {user.name} - już powiązany z person_id={user.person_id}")
continue
user_name = user.name.lower().strip()
if user_name in person_lookup:
person = person_lookup[user_name]
user.person_id = person.id
matched += 1
print(f"{user.name} → Person #{person.id} ({person.full_name()})")
else:
print(f"{user.name} - brak dopasowania")
if matched > 0:
db.commit()
print(f"\n✓ Powiązano {matched} użytkowników z osobami")
else:
print("\nBrak nowych powiązań do zapisania")
db.close()
print("Gotowe!")
if __name__ == "__main__":
main()

View File

@ -265,19 +265,22 @@
<div class="attendees-list">
{% for attendee in event.attendees|sort(attribute='user.name') %}
<div class="attendee-badge">
{% if attendee.user.company %}
<a href="{{ url_for('company_detail_by_slug', slug=attendee.user.company.slug) }}" class="attendee-name">
{# Person badge - link to person profile if person_id exists #}
{% if attendee.user.person_id %}
<a href="{{ url_for('person_detail', person_id=attendee.user.person_id) }}" class="attendee-name">
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
{{ attendee.user.name or attendee.user.email.split('@')[0] }}
</a>
<a href="{{ url_for('company_detail_by_slug', slug=attendee.user.company.slug) }}" class="attendee-company">
{% elif attendee.user.company %}
<a href="{{ url_for('company_detail_by_slug', slug=attendee.user.company.slug) }}" class="attendee-name">
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M19 21V5a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v5m-4 0h4"></path>
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
{{ attendee.user.company.name }}
{{ attendee.user.name or attendee.user.email.split('@')[0] }}
</a>
{% else %}
<span class="attendee-name">
@ -288,6 +291,16 @@
{{ attendee.user.name or attendee.user.email.split('@')[0] }}
</span>
{% endif %}
{# Company badge - always link to company profile #}
{% if attendee.user.company %}
<a href="{{ url_for('company_detail_by_slug', slug=attendee.user.company.slug) }}" class="attendee-company">
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M19 21V5a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v5m-4 0h4"></path>
</svg>
{{ attendee.user.company.name }}
</a>
{% endif %}
</div>
{% endfor %}
</div>