From 4b38f8953c956a480fd252c55e6e721f96a8db51 Mon Sep 17 00:00:00 2001 From: Maciej Pienczyn Date: Wed, 28 Jan 2026 20:51:15 +0100 Subject: [PATCH] feat(categories): Hierarchiczna struktura kategorii MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- database.py | 13 +- .../migrations/030_add_category_hierarchy.sql | 30 +++++ scripts/assign_category_parents.py | 112 ++++++++++++++++++ 3 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 database/migrations/030_add_category_hierarchy.sql create mode 100644 scripts/assign_category_parents.py diff --git a/database.py b/database.py index 9a14cdd..da6de12 100644 --- a/database.py +++ b/database.py @@ -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): diff --git a/database/migrations/030_add_category_hierarchy.sql b/database/migrations/030_add_category_hierarchy.sql new file mode 100644 index 0000000..ba818ec --- /dev/null +++ b/database/migrations/030_add_category_hierarchy.sql @@ -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; diff --git a/scripts/assign_category_parents.py b/scripts/assign_category_parents.py new file mode 100644 index 0000000..2e04e15 --- /dev/null +++ b/scripts/assign_category_parents.py @@ -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()