feat(categories): Hierarchiczna struktura kategorii

- Dodanie parent_id do tabeli categories
- Model Category z relacją parent/subcategories
- 4 główne grupy: Usługi, Budownictwo, Handel, Produkcja
- Skrypt assign_category_parents.py do przypisania podkategorii
- Migracja 030_add_category_hierarchy.sql

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-01-28 20:51:15 +01:00
parent 1fb938feb3
commit 4b38f8953c
3 changed files with 154 additions and 1 deletions

View File

@ -233,7 +233,7 @@ class User(Base, UserMixin):
# ============================================================
class Category(Base):
"""Company categories"""
"""Company categories with hierarchical structure"""
__tablename__ = 'categories'
id = Column(Integer, primary_key=True)
@ -244,7 +244,18 @@ class Category(Base):
sort_order = Column(Integer, default=0)
created_at = Column(DateTime, default=datetime.now)
# Hierarchical structure
parent_id = Column(Integer, ForeignKey('categories.id'), nullable=True)
display_order = Column(Integer, default=0)
# Relationships
companies = relationship('Company', back_populates='category')
parent = relationship('Category', remote_side=[id], backref='subcategories')
@property
def is_main_category(self):
"""Check if this is a main (parent) category"""
return self.parent_id is None
class Company(Base):

View File

@ -0,0 +1,30 @@
-- Migration: Add category hierarchy (parent-child structure)
-- Date: 2026-01-28
-- Description: Adds parent_id to categories for 4 main groups + subcategories
-- Add parent_id column
ALTER TABLE categories ADD COLUMN IF NOT EXISTS parent_id INTEGER REFERENCES categories(id);
ALTER TABLE categories ADD COLUMN IF NOT EXISTS display_order INTEGER DEFAULT 0;
-- Create index for parent lookup
CREATE INDEX IF NOT EXISTS idx_categories_parent_id ON categories(parent_id);
-- Insert 4 main categories (parents)
INSERT INTO categories (name, slug, description, parent_id, display_order)
VALUES
('Usługi', 'uslugi', 'Firmy usługowe - IT, doradztwo, marketing, prawo, finanse', NULL, 1),
('Budownictwo', 'budownictwo-grupa', 'Budownictwo, instalacje, architektura, nieruchomości', NULL, 2),
('Handel', 'handel', 'Handel, hurtownie, motoryzacja, hotelarstwo', NULL, 3),
('Produkcja', 'produkcja-grupa', 'Produkcja, wytwarzanie, rolnictwo', NULL, 4)
ON CONFLICT (slug) DO NOTHING;
-- Get IDs of main categories (will be used in next migration step)
-- Note: The actual parent assignment will be done via Python script
-- because we need to know the exact IDs
-- Comments
COMMENT ON COLUMN categories.parent_id IS 'Parent category ID for hierarchical structure (NULL = main category)';
COMMENT ON COLUMN categories.display_order IS 'Order for display in UI';
-- Grant permissions
GRANT ALL ON TABLE categories TO nordabiz_app;

View File

@ -0,0 +1,112 @@
#!/usr/bin/env python3
"""
Skrypt do przypisania istniejących kategorii do głównych grup.
Uruchom po migracji 030_add_category_hierarchy.sql
"""
import os
import sys
# Add parent directory to path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from database import SessionLocal, Category
# Mapowanie kategorii do głównych grup
# Klucz: slug głównej grupy, wartość: lista slugów podkategorii
CATEGORY_MAPPING = {
'uslugi': [
'it-technologie',
'it-telekomunikacja',
'marketing',
'uslugi-prawne',
'ksiegowosc-finanse',
'uslugi-biznesowe',
'bezpieczenstwo-ochrona',
'media',
],
'budownictwo-grupa': [
'budownictwo',
'hvac-instalacje',
'architektura-projektowanie',
'energia-oze',
'nieruchomosci',
],
'handel': [
'handel-hurtownie',
'motoryzacja',
'hotelarstwo',
],
'produkcja-grupa': [
'produkcja',
'rolnictwo',
'inne',
],
}
def main():
db = SessionLocal()
try:
# Znajdź lub utwórz główne kategorie
main_categories = {}
for main_slug, subcategory_slugs in CATEGORY_MAPPING.items():
main_cat = db.query(Category).filter(Category.slug == main_slug).first()
if not main_cat:
print(f"[!] Główna kategoria '{main_slug}' nie istnieje - tworzę...")
# Utwórz główną kategorię
name_map = {
'uslugi': 'Usługi',
'budownictwo-grupa': 'Budownictwo',
'handel': 'Handel',
'produkcja-grupa': 'Produkcja',
}
main_cat = Category(
name=name_map.get(main_slug, main_slug),
slug=main_slug,
description=f'Główna kategoria: {name_map.get(main_slug, main_slug)}',
parent_id=None,
display_order=list(CATEGORY_MAPPING.keys()).index(main_slug) + 1
)
db.add(main_cat)
db.flush() # Get ID
main_categories[main_slug] = main_cat
print(f"[✓] Główna kategoria: {main_cat.name} (ID: {main_cat.id})")
# Przypisz podkategorie
for sub_slug in subcategory_slugs:
sub_cat = db.query(Category).filter(Category.slug == sub_slug).first()
if sub_cat:
if sub_cat.parent_id != main_cat.id:
sub_cat.parent_id = main_cat.id
print(f"{sub_cat.name} przypisana do {main_cat.name}")
else:
print(f"{sub_cat.name} już przypisana")
else:
print(f" [!] Kategoria '{sub_slug}' nie istnieje - pomijam")
db.commit()
print("\n[✓] Przypisanie kategorii zakończone!")
# Pokaż podsumowanie
print("\n=== PODSUMOWANIE ===")
for main_slug, main_cat in main_categories.items():
subcats = db.query(Category).filter(Category.parent_id == main_cat.id).all()
print(f"\n{main_cat.name} ({len(subcats)} podkategorii):")
for sub in subcats:
company_count = len(sub.companies) if sub.companies else 0
print(f" - {sub.name}: {company_count} firm")
except Exception as e:
print(f"[!] Błąd: {e}")
db.rollback()
raise
finally:
db.close()
if __name__ == '__main__':
main()