nordabiz/blueprints/admin/routes_roadmap.py
Maciej Pienczyn e46233c09c
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
feat: add public tenders aggregator to roadmap
New roadmap item: automated procurement/tender aggregation from
BIP, e-Zamówienia, TED, municipal websites. Requested by Daniel
Kochański (Stalpunkt). Priority: high.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 22:55:48 +02:00

139 lines
6.4 KiB
Python

"""
Admin Roadmap Routes
====================
Product roadmap — feature requests from members and strategic plans.
"""
import logging
from flask import render_template
from flask_login import login_required
from . import bp
from database import SystemRole
from utils.decorators import role_required
logger = logging.getLogger(__name__)
ROADMAP_ITEMS = [
{
'id': 1,
'title': 'Wiele lokalizacji firmy na mapie',
'description': 'Firmy z kilkoma oddziałami/punktami mogą dodać wiele adresów. '
'Każdy wyświetlany na profilu z linkiem do Google Maps.',
'details': [
'<strong>1. Model</strong> — nowa tabela <code>company_locations</code>: id, company_id (FK CASCADE), '
'label (np. "Biuro główne", "Magazyn"), address_street, address_city, address_postal, '
'lat, lng (opcjonalne — na przyszłość), is_primary, source, created_at. '
'Relacja na Company: locations = relationship(CompanyLocation, cascade=all delete-orphan)',
'<strong>2. Migracja SQL</strong> — <code>database/migrations/100_company_locations.sql</code>: '
'CREATE TABLE IF NOT EXISTS, indeks na company_id, GRANT na nordabiz_app. '
'Migracja danych: INSERT z istniejących address_* dla firm z adresem (żeby nie stracić danych)',
'<strong>3. Formularz edycji</strong> — nowa sekcja "Dodatkowe lokalizacje" w zakładce Kontakt '
'(<code>templates/company_edit.html</code>). Dynamiczne wiersze wzorowane na CompanyWebsite: '
'label + street + postal + city + is_primary radio + przycisk usuń. Max 10 lokalizacji. '
'JS: makeLocationRow(), removeLocationRow(), reindexLocationRadios()',
'<strong>4. Zapis</strong> — funkcja <code>_save_locations()</code> w '
'<code>blueprints/public/routes_company_edit.py</code>. '
'Strategia delete-and-reinsert (jak websites): usuń wszystkie WHERE company_id AND source IN (null, manual_edit), '
'wstaw z formularza: location_labels[], location_streets[], location_postals[], location_cities[], location_primary. '
'Aktualizacja address_* na Company z primary location',
'<strong>5. Profil firmy</strong> — wyświetlanie w <code>templates/company_detail.html</code> (linia ~1078). '
'Jeśli firma ma lokalizacje → wyświetl wszystkie z labelami, każda z linkiem "Pokaż na mapie →" '
'(Google Maps Search URL). Primary oznaczona jako "Siedziba główna". '
'Fallback na obecny address_full jeśli brak lokalizacji',
'<strong>6. Route</strong> — w <code>blueprints/public/routes.py</code> company_detail(): '
'eager load company.locations i przekazać do szablonu',
'<strong>Czego NIE robimy:</strong> brak embedded Google Maps (iframe/API), '
'brak geocodingu (lat/lng pola przygotowane na przyszłość), '
'brak edycji lokalizacji przez admina — tylko przez właściciela firmy',
],
'requested_by': 'Daniel Kochański (Stalpunkt)',
'date': '2026-04-09',
'priority': 'medium',
'status': 'planned',
'category': 'feature',
},
{
'id': 2,
'title': 'Agregator przetargów publicznych',
'description': 'Automatyczne pobieranie przetargów z portali publicznych '
'(BIP, e-Zamówienia, TED, strony urzędów miast/gmin, spółki miejskie) '
'i wyświetlanie na portalu z możliwością filtrowania wg branży.',
'details': [
'<strong>1. Źródła danych</strong> — BIP (scraping RSS/HTML), e-Zamówienia (API publiczne), '
'TED/Tenders Electronic Daily (API EU), strony urzędów miast i gmin (scraping), spółki miejskie',
'<strong>2. Model danych</strong> — nowa tabela <code>public_tenders</code>: id, title, description, '
'source (bip/ezamowienia/ted/municipal), source_url, published_at, deadline, '
'value_estimate, category/tags, location, status (active/expired), fetched_at',
'<strong>3. Cron/scheduler</strong> — cykliczne pobieranie (np. co 4h), deduplikacja po source_url, '
'parsowanie i kategoryzowanie treści przez AI (dopasowanie do branż firm w Izbie)',
'<strong>4. Panel użytkownika</strong> — nowa zakładka "Przetargi" z filtrowaniem: '
'branża, lokalizacja, wartość, termin składania, źródło. Subskrypcja powiadomień wg filtrów',
'<strong>5. Integracja z NordaGPT</strong> — chatbot może wyszukiwać przetargi pasujące '
'do profilu firmy użytkownika i proaktywnie sugerować nowe ogłoszenia',
'<strong>6. Powiadomienia</strong> — email/in-app alert gdy pojawi się przetarg pasujący '
'do zapisanych filtrów użytkownika lub profilu firmy',
'<strong>Ryzyka:</strong> scraping BIP/urzędów jest kruchy (zmienne layouty), '
'e-Zamówienia API może wymagać rejestracji, TED API wymaga klucza UE. '
'Rekomendacja: zacząć od e-Zamówienia (najstabilniejsze API)',
],
'requested_by': 'Daniel Kochański (Stalpunkt)',
'date': '2026-04-09',
'priority': 'high',
'status': 'planned',
'category': 'feature',
},
]
STATUS_LABELS = {
'planned': ('Planowane', '#6366f1'),
'in_progress': ('W trakcie', '#f59e0b'),
'done': ('Zrealizowane', '#10b981'),
'on_hold': ('Wstrzymane', '#94a3b8'),
}
PRIORITY_LABELS = {
'high': ('Wysoki', '#ef4444'),
'medium': ('Średni', '#f59e0b'),
'low': ('Niski', '#6b7280'),
}
CATEGORY_LABELS = {
'feature': 'Nowa funkcjonalność',
'improvement': 'Ulepszenie',
'bugfix': 'Naprawa błędu',
}
@bp.route('/roadmap')
@login_required
@role_required(SystemRole.ADMIN)
def admin_roadmap():
"""Product roadmap — feature requests and development plans."""
planned = [i for i in ROADMAP_ITEMS if i['status'] == 'planned']
in_progress = [i for i in ROADMAP_ITEMS if i['status'] == 'in_progress']
done = [i for i in ROADMAP_ITEMS if i['status'] == 'done']
return render_template(
'admin/roadmap.html',
planned=planned,
in_progress=in_progress,
done=done,
status_labels=STATUS_LABELS,
priority_labels=PRIORITY_LABELS,
category_labels=CATEGORY_LABELS,
)