#!/usr/bin/env python3 """ Import Board Meeting 2/2026 (04.02.2026) data. One-time script to create BoardMeeting record with full agenda, attendance, proceedings and decisions from the February 4 session. Usage: # Local dev: python3 scripts/import_board_meeting_2_2026.py # Production: DATABASE_URL=$(grep DATABASE_URL .env | cut -d'=' -f2) \ /var/www/nordabiznes/venv/bin/python3 scripts/import_board_meeting_2_2026.py """ import os import sys from datetime import date, time, datetime # Add project root to path sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from database import SessionLocal, BoardMeeting, User def get_user_by_email(db, email): """Find user by email, return None if not found.""" return db.query(User).filter(User.email == email).first() def main(): db = SessionLocal() try: # Check if meeting already exists existing = db.query(BoardMeeting).filter( BoardMeeting.meeting_number == 2, BoardMeeting.year == 2026 ).first() if existing: print(f"Meeting 2/2026 already exists (id={existing.id}). Skipping.") return existing.id # Resolve key users chairperson = get_user_by_email(db, 'leszek@rotor.pl') secretary = get_user_by_email(db, 'magdalena.kloska@norda-biznes.info') # Fallback: try partial match for secretary if not secretary: secretary = db.query(User).filter(User.email.like('%kloska%')).first() # Find admin/office_manager as creator creator = db.query(User).filter( User.role.in_(['ADMIN', 'OFFICE_MANAGER']), User.is_active == True ).first() if not creator: print("ERROR: No ADMIN or OFFICE_MANAGER user found. Cannot set created_by.") return None print(f"Chairperson: {chairperson.name if chairperson else 'NOT FOUND'} (id={chairperson.id if chairperson else '?'})") print(f"Secretary: {secretary.name if secretary else 'NOT FOUND'} (id={secretary.id if secretary else '?'})") print(f"Creator: {creator.name} (id={creator.id})") # Build attendance map: email -> status PRESENT_EMAILS = [ 'leszek@rotor.pl', # Glaza 'andrzej.gorczycki@zukwejherowo.pl', # Gorczycki 'pawel.kwidzinski@norda-biznes.info', # Kwidzinski 'dariusz.schmidtke@tkchopin.pl', # Schmidtke 'artur.wiertel@norda-biznes.info', # Wiertel 'a.jedrzejewski@scrol.pl', # Jedrzejewski 'info@greenhousesystems.pl', # Piechocka 'jm@hebel-masiak.pl', # Masiak 'kuba@bormax.com.pl', # Bornowski 'pawel.piechota@norda-biznes.info', # Piechota 'radoslaw@skwarlo.pl', # Skwarlo 'roman@sigmabudownictwo.pl', # Wiercinski 'mjwesierski@gmail.com', # Wesierski ] ABSENT_EMAILS = [ 'iwonamusial@cristap.pl', # Musial 'krzysztof.kubis@sibuk.pl', # Kubis 'jacek.pomieczynski@eura-tech.eu', # Pomieczynski ] attendance = {} for email in PRESENT_EMAILS: user = get_user_by_email(db, email) if user: # Generate initials from name initials = ''.join(p[0].upper() for p in (user.name or '').split() if p) or '' attendance[str(user.id)] = { 'status': 'present', 'present': True, 'initials': initials } else: print(f" WARNING: User not found: {email}") for email in ABSENT_EMAILS: user = get_user_by_email(db, email) if user: initials = ''.join(p[0].upper() for p in (user.name or '').split() if p) or '' attendance[str(user.id)] = { 'status': 'absent', 'present': False, 'initials': initials } else: print(f" WARNING: User not found: {email}") present_count = sum(1 for a in attendance.values() if a['present']) print(f"Attendance: {present_count}/{len(attendance)} present") # Agenda items (15 points) agenda_items = [ {"time_start": "16:00", "time_end": "16:05", "title": "Otwarcie posiedzenia i przyjęcie programu"}, {"time_start": "16:05", "time_end": "16:10", "title": "Przyjęcie protokołu z posiedzenia nr 1/2026"}, {"time_start": "16:10", "time_end": "16:30", "title": "Prezentacja i głosowanie nad kandydatami na nowych członków Izby"}, {"time_start": "16:30", "time_end": "16:45", "title": "Informacja o stanie finansów Izby"}, {"time_start": "16:45", "time_end": "17:00", "title": "Omówienie składek członkowskich na 2026 rok"}, {"time_start": "17:00", "time_end": "17:15", "title": "Spotkanie dot. marketingu i komunikacji"}, {"time_start": "17:15", "time_end": "17:30", "title": "Planowanie grilla integracyjnego"}, {"time_start": "17:30", "time_end": "17:45", "title": "Obchody 30-lecia Izby"}, {"time_start": "17:45", "time_end": "18:00", "title": "Konkurs Tytani Przedsiębiorczości"}, {"time_start": "18:00", "time_end": "18:10", "title": "Aplikacja NordaBiznes — aktualizacja"}, {"time_start": "18:10", "time_end": "18:20", "title": "Współpraca z samorządem"}, {"time_start": "18:20", "time_end": "18:30", "title": "Walne Zgromadzenie Członków — termin"}, {"time_start": "18:30", "time_end": "18:40", "title": "Ustalenie terminu kolejnego posiedzenia Rady"}, {"time_start": "18:40", "time_end": "18:55", "title": "Wolne wnioski i dyskusja"}, {"time_start": "18:55", "time_end": "19:00", "title": "Zamknięcie posiedzenia"}, ] # Proceedings (key discussions and decisions) proceedings = [ { "agenda_item": 0, "title": "Otwarcie posiedzenia i przyjęcie programu", "discussion": "Prezes Leszek Glaza otworzył posiedzenie, powitał obecnych członków Rady. Stwierdzono kworum (13 z 16 członków). Program posiedzenia przyjęto jednogłośnie.", "decisions": ["Program posiedzenia nr 2/2026 przyjęty jednogłośnie"], "tasks": [] }, { "agenda_item": 1, "title": "Przyjęcie protokołu z posiedzenia nr 1/2026", "discussion": "Protokół z posiedzenia Rady nr 1/2026 z dnia 07.01.2026 został przedstawiony członkom Rady.", "decisions": ["Protokół z posiedzenia nr 1/2026 przyjęty jednogłośnie"], "tasks": [] }, { "agenda_item": 2, "title": "Prezentacja i głosowanie nad kandydatami na nowych członków Izby", "discussion": "Przedstawiono 5 kandydatów na nowych członków Izby Przedsiębiorców NORDA:\n\n1. Konkol Sp. z o.o. — branża budowlana, rekomendacja od członka Rady\n2. Ibet Sp. z o.o. — producent kostki brukowej i elementów betonowych\n3. Audioline — usługi audiologiczne\n4. PC Invest — inwestycje i nieruchomości\n5. Pacific Sun / Fiume — branża turystyczna i gastronomiczna\n\nKażdy kandydat został krótko przedstawiony wraz z uzasadnieniem rekomendacji.", "decisions": [ "Przyjęto jednogłośnie firmę Konkol Sp. z o.o. jako nowego członka Izby", "Przyjęto jednogłośnie firmę Ibet Sp. z o.o. jako nowego członka Izby", "Przyjęto jednogłośnie firmę Audioline jako nowego członka Izby", "Przyjęto jednogłośnie firmę PC Invest jako nowego członka Izby", "Przyjęto jednogłośnie firmę Pacific Sun / Fiume jako nowego członka Izby" ], "tasks": ["Przygotować dokumenty przyjęcia dla 5 nowych członków"] }, { "agenda_item": 3, "title": "Informacja o stanie finansów Izby", "discussion": "Przedstawiono bieżący stan finansów Izby. Omówiono wpływy ze składek oraz wydatki operacyjne.", "decisions": [], "tasks": [] }, { "agenda_item": 4, "title": "Omówienie składek członkowskich na 2026 rok", "discussion": "Dyskutowano nad wysokością składek członkowskich od stycznia 2026. Zaproponowano podział na małe i duże firmy.", "decisions": [ "Składki od 01.2026: małe firmy 200 zł/miesiąc, duże firmy 300 zł/miesiąc (głosowanie: 12 za, 0 przeciw, 1 wstrzymujący się)" ], "tasks": ["Przygotować informację o nowych stawkach składek dla członków"] }, { "agenda_item": 5, "title": "Spotkanie dot. marketingu i komunikacji", "discussion": "Omówiono potrzebę spotkania roboczego dot. strategii social media i komunikacji marketingowej Izby.", "decisions": [ "Spotkanie ws. social media wyznaczone na 18.02.2026, godz. 09:00, Ekofabryka" ], "tasks": ["Przygotować spotkanie ws. social media na 18.02.2026"] }, { "agenda_item": 6, "title": "Planowanie grilla integracyjnego", "discussion": "Omówiono organizację grilla integracyjnego dla członków Izby. Zaproponowano termin i lokalizację.", "decisions": [ "Grill integracyjny: 16.05.2026, strzelnica / Bractwo Kurkowe Wejherowo" ], "tasks": ["Zarezerwować lokalizację na grill integracyjny 16.05.2026"] }, { "agenda_item": 7, "title": "Obchody 30-lecia Izby", "discussion": "Omówiono plany obchodów 30-lecia istnienia Izby Przedsiębiorców NORDA. Powołano komitet organizacyjny.", "decisions": [ "Komitet 30-lecia Izby: DS, AG, RW + Zarząd" ], "tasks": ["Komitet 30-lecia — rozpocząć planowanie obchodów"] }, { "agenda_item": 8, "title": "Konkurs Tytani Przedsiębiorczości", "discussion": "Omówiono stan przygotowań do kolejnej edycji konkursu Tytani Przedsiębiorczości Powiatu Wejherowskiego.", "decisions": [], "tasks": [] }, { "agenda_item": 9, "title": "Aplikacja NordaBiznes — aktualizacja", "discussion": "Przedstawiono postępy w rozwoju aplikacji NordaBiznes Partner — katalogu firmowego i platformy networkingowej Izby.", "decisions": [], "tasks": [] }, { "agenda_item": 10, "title": "Współpraca z samorządem", "discussion": "Omówiono bieżącą współpracę z samorządem lokalnym.", "decisions": [], "tasks": [] }, { "agenda_item": 11, "title": "Walne Zgromadzenie Członków — termin", "discussion": "Ustalono termin i miejsce Walnego Zgromadzenia Członków Izby (zgromadzenie wyborcze).", "decisions": [ "Walne Zgromadzenie Członków (wyborcze): 08.06.2026, godz. 14:00, Urząd Miasta Wejherowo" ], "tasks": ["Przygotować zawiadomienia o Walnym Zgromadzeniu"] }, { "agenda_item": 12, "title": "Ustalenie terminu kolejnego posiedzenia Rady", "discussion": "Zaproponowano termin kolejnego posiedzenia Rady Izby.", "decisions": [ "Propozycja kolejnego posiedzenia Rady: 04.03.2026, godz. 16:00" ], "tasks": [] }, ] # Create meeting meeting = BoardMeeting( meeting_number=2, year=2026, meeting_date=date(2026, 2, 4), start_time=time(16, 0), end_time=time(19, 0), location='Siedziba Izby', chairperson_id=chairperson.id if chairperson else None, secretary_id=secretary.id if secretary else None, guests=None, agenda_items=agenda_items, attendance=attendance, quorum_count=present_count, quorum_confirmed=present_count >= 9, proceedings=proceedings, status=BoardMeeting.STATUS_PROTOCOL_PUBLISHED, created_by=creator.id, created_at=datetime(2026, 2, 4, 19, 0), agenda_published_at=datetime(2026, 2, 4, 16, 0), protocol_published_at=datetime(2026, 2, 20, 12, 0), ) db.add(meeting) db.commit() print(f"\nMeeting 2/2026 created successfully (id={meeting.id})") print(f" Date: 04.02.2026, 16:00-19:00") print(f" Attendance: {present_count}/16 (quorum: {'YES' if meeting.quorum_confirmed else 'NO'})") print(f" Agenda items: {len(agenda_items)}") print(f" Proceedings: {len(proceedings)}") print(f" Status: {meeting.status}") return meeting.id except Exception as e: db.rollback() print(f"ERROR: {e}") import traceback traceback.print_exc() return None finally: db.close() if __name__ == '__main__': meeting_id = main() if meeting_id: print(f"\nDone. Meeting ID: {meeting_id}") else: print("\nFailed to import meeting.") sys.exit(1)