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
Add document management routes (upload, download, soft-delete) to board blueprint, link BoardDocument to BoardMeeting via meeting_id FK, add documents section to meeting view template, and include import scripts for meeting 2/2026 data and PDFs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
301 lines
14 KiB
Python
301 lines
14 KiB
Python
#!/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)
|