Files
vergabe-teilnahme/vergabe_teilnahme/apps/core/services.py
2026-05-08 14:26:48 +02:00

107 lines
3.2 KiB
Python

from datetime import date
from decimal import Decimal
PHASEN = [
(1, 'Recherche & Unterlagen'),
(2, 'Teilnahmeentscheidung'),
(3, 'Detaillierte Durchsicht'),
(4, 'Bieterfragen & Klärung'),
(5, 'Preismodell'),
(6, 'Unterlagen finalisieren'),
(7, 'Abgabe'),
(8, 'Zuschlag / Nachbetrachtung'),
]
# Maps Ausschreibung.status integer to phase number
STATUS_TO_PHASE = {
1: 1, 2: 1, # Recherche
3: 2, # Teilnahmeentscheidung
4: 3, 5: 3, # Durchsicht
6: 4, 7: 4, # Bieterfragen
8: 5, # Preise
9: 6, # Finalisierung
10: 7, 11: 7, # Abgabe
12: 8, 13: 8, # Nachbetrachtung
}
def build_phase_nav(ausschreibung, current_url=''):
aktuelle_phase = STATUS_TO_PHASE.get(ausschreibung.status, 1)
base = f'/ausschreibungen/{ausschreibung.pk}'
phase_urls = {
1: f'{base}/',
2: f'{base}/teilnahmeentscheidung/',
3: f'{base}/anforderungen/',
4: f'{base}/bieterfragen/',
5: f'{base}/preise/',
6: f'{base}/dokumente/',
7: f'{base}/abgabe/',
8: f'{base}/nachbetrachtung/',
}
return [
{
'nummer': num,
'name': name,
'url': phase_urls[num],
'aktiv': num == aktuelle_phase,
'erledigt': num < aktuelle_phase,
'warnung': False,
}
for num, name in PHASEN
]
def get_deadline_warnings(ausschreibung):
"""Gibt Liste von Warnungen für nahende Fristen zurück."""
warnings = []
heute = date.today()
if ausschreibung.bieterfragen_bis:
delta = (ausschreibung.bieterfragen_bis - heute).days
if delta <= 3:
warnings.append({
'typ': 'bieterfragen',
'tage': delta,
'farbe': 'red' if delta <= 1 else 'amber',
})
if ausschreibung.abgabe_bis:
abgabe_date = (
ausschreibung.abgabe_bis.date()
if hasattr(ausschreibung.abgabe_bis, 'date')
else ausschreibung.abgabe_bis
)
delta = (abgabe_date - heute).days
if delta <= 14:
warnings.append({
'typ': 'abgabe',
'tage': delta,
'farbe': 'red' if delta <= 3 else 'amber',
})
return warnings
def gewichteter_durchschnitt(preispunkte, feld='einzelpreis'):
"""Berechnet gewichteten Durchschnitt für Preispunkte.
Punkte mit Gewicht 0,0 werden ausgeschlossen.
Gibt None zurück wenn keine verwertbaren Punkte vorhanden.
"""
relevante = [
p for p in preispunkte
if getattr(p, feld) is not None and p.vergleichsgewicht > 0
]
if not relevante:
return None
summe_gewichte = sum(p.vergleichsgewicht for p in relevante)
if summe_gewichte == 0:
return None
summe = sum(getattr(p, feld) * p.vergleichsgewicht for p in relevante)
werte = [getattr(p, feld) for p in relevante]
return {
'wert': summe / summe_gewichte,
'summe_gewichte': summe_gewichte,
'anzahl': len(relevante),
'minimum': min(werte),
'maximum': max(werte),
'ungewichtet': sum(werte) / len(werte),
}