generated from coulomb/repo-seed
504 lines
19 KiB
Markdown
504 lines
19 KiB
Markdown
---
|
||
id: WP-0002
|
||
title: Fachmodelle — alle Django-Models, Migrationen, Admin
|
||
status: done
|
||
phase: 2-of-12
|
||
created: "2026-05-08"
|
||
depends_on: WP-0001
|
||
---
|
||
|
||
# WP-0002 — Fachmodelle
|
||
|
||
Implementiert alle Django-Modelle gemäß `wiki/ArchitectureBlueprint.md` Abschnitt 5.
|
||
Alle Modelle erben von `FlexibleModel`. Am Ende: alle Migrationen, Admin-Registrierung
|
||
und ein Management-Command für Seed-Daten.
|
||
|
||
**Arbeitsverzeichnis:** `/home/worsch/vergabe-teilnahme/`
|
||
**Apps-Pfad:** `vergabe_teilnahme/apps/`
|
||
|
||
---
|
||
|
||
```task
|
||
id: WP-0002-T01
|
||
title: Accounts-App: Mitarbeiter-Modell (AbstractUser)
|
||
status: done
|
||
|
||
Datei: `vergabe_teilnahme/apps/accounts/models.py`
|
||
|
||
```python
|
||
from django.contrib.auth.models import AbstractUser
|
||
from django.db import models
|
||
|
||
class Mitarbeiter(AbstractUser):
|
||
ROLLE_CHOICES = [
|
||
('bid_manager', 'Bid Manager'),
|
||
('fachexperte', 'Fachverantwortlicher'),
|
||
('vertrieb', 'Vertrieb / Account Management'),
|
||
('pricing', 'Pricing / Controlling'),
|
||
('recht', 'Recht / Compliance'),
|
||
('geschaeftsfuehrung', 'Geschäftsführung'),
|
||
('projektleitung','Projektleitung Umsetzung'),
|
||
('admin', 'Administrator'),
|
||
]
|
||
rolle = CharField(max_length=30, choices=ROLLE_CHOICES, blank=True)
|
||
mobilnummer = CharField(max_length=50, blank=True)
|
||
organisationseinheit = CharField(max_length=200, blank=True)
|
||
|
||
def __str__(self):
|
||
return self.get_full_name() or self.username
|
||
```
|
||
|
||
Registriere in `accounts/admin.py` mit `UserAdmin`.
|
||
Setze `AUTH_USER_MODEL = 'accounts.Mitarbeiter'` in `settings/base.py` (bereits in T02 von WP-0001 vorbereitet — prüfen).
|
||
```
|
||
|
||
```task
|
||
id: WP-0002-T02
|
||
title: Core-App: FlexibleModel-Mixin und Basis-Infrastruktur
|
||
status: done
|
||
|
||
Datei: `vergabe_teilnahme/apps/core/models.py`
|
||
|
||
Implementiere:
|
||
1. `FlexibleModel(Model)` — abstract base:
|
||
- Hat `GenericRelation` auf `CustomAttribute`
|
||
- Methode `get_hidden_fields()` — liest `EntityFieldConfig` für `self._meta.model_name`
|
||
- Methode `get_visible_field_names()` — gibt Liste nicht-ausgeblendeter Felder zurück
|
||
- `class Meta: abstract = True`
|
||
|
||
2. `EntityFieldConfig(Model)` — globale Feldkonfiguration pro Entitätstyp:
|
||
- `entity_type` CharField(100) — model_name slug (z. B. 'subunternehmer')
|
||
- `field_name` CharField(100)
|
||
- `is_hidden` BooleanField(default=False)
|
||
- `display_label` CharField(200, blank=True) — Umbenennung
|
||
- `sort_order` PositiveSmallIntegerField(default=0)
|
||
- `Meta: unique_together = ('entity_type', 'field_name')`
|
||
|
||
3. `CustomAttribute(Model)` — generisches Key-Value-Attribut:
|
||
- `content_type` FK(ContentType, CASCADE)
|
||
- `object_id` PositiveIntegerField()
|
||
- `content_object` GenericForeignKey()
|
||
- `key` CharField(100) — slug
|
||
- `label` CharField(200) — Anzeigename
|
||
- `value` TextField(blank=True)
|
||
- `data_type` CharField(20, choices=[text/number/date/boolean/url/email], default='text')
|
||
- `sort_order` PositiveSmallIntegerField(default=0)
|
||
- `created_at` DateTimeField(auto_now_add=True)
|
||
- `Meta: ordering = ['sort_order', 'created_at']`
|
||
- `Index` auf `(content_type, object_id)`
|
||
|
||
4. `Freigabe(Model)` — generische Freigabe:
|
||
- `content_type` FK(ContentType, CASCADE)
|
||
- `object_id` PositiveIntegerField()
|
||
- `content_object` GenericForeignKey()
|
||
- `freigabe_typ` CharField(30, choices=[teilnahme/ablehnung/recht/preis/abgabe/standarddokument/referenz])
|
||
- `freigebende_person` FK(settings.AUTH_USER_MODEL, PROTECT)
|
||
- `status` CharField(20, choices=[erteilt/abgelehnt/ausstehend], default='erteilt')
|
||
- `kommentar` TextField(blank=True)
|
||
- `timestamp` DateTimeField(auto_now_add=True)
|
||
- `Meta: ordering = ['-timestamp']`
|
||
|
||
Alle vier in `core/admin.py` registrieren.
|
||
```
|
||
|
||
```task
|
||
id: WP-0002-T03
|
||
title: Core-App: services.py mit Utility-Funktionen
|
||
status: done
|
||
|
||
Datei: `vergabe_teilnahme/apps/core/services.py`
|
||
|
||
Implementiere folgende Funktionen (ohne Django-Import-Fehler zur Importzeit):
|
||
|
||
```python
|
||
from datetime import date
|
||
from decimal import Decimal
|
||
|
||
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:
|
||
delta = (ausschreibung.abgabe_bis.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. Gibt None zurück wenn keine verwertbaren Punkte."""
|
||
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),
|
||
}
|
||
```
|
||
|
||
Schreibe Tests in `vergabe_teilnahme/apps/core/tests/test_services.py`:
|
||
- Test gewichteter_durchschnitt mit Beispiel aus Blueprint (100×1,0 + 80×0,2 + 110×1,2 = 103,33)
|
||
- Test mit leerem Input → None
|
||
- Test mit allen Gewichten 0 → None
|
||
```
|
||
|
||
```task
|
||
id: WP-0002-T04
|
||
title: Ausschreibungen-App: Ausschreibung-Modell
|
||
status: done
|
||
|
||
Datei: `vergabe_teilnahme/apps/ausschreibungen/models.py`
|
||
|
||
Implementiere `Ausschreibung(FlexibleModel)` gemäß Blueprint Abschnitt 5.1.
|
||
Import von FlexibleModel: `from vergabe_teilnahme.apps.core.models import FlexibleModel`
|
||
|
||
Felder exakt wie im Blueprint definiert (STATUS_CHOICES 1-13, TEILNAHME_CHOICES,
|
||
alle Datums-/Fristen-Felder, erstellt_am/geaendert_am auto).
|
||
|
||
Methoden:
|
||
- `__str__` → `self.titel`
|
||
- `property ist_aktiv` → status in range(1, 10)
|
||
- `property naechste_frist` → das nächste nicht-vergangene Datum aus (bieterfragen_bis, abgabe_bis.date())
|
||
- `property phase_nummer` → min(8, max(1, self.status)) — Phasennummer für Navigator
|
||
|
||
`Meta: ordering = ['-erstellt_am']`
|
||
|
||
Admin-Registrierung mit list_display=['titel', 'ausschreiber', 'status', 'abgabe_bis'].
|
||
```
|
||
|
||
```task
|
||
id: WP-0002-T05
|
||
title: Lose-App: Los- und Anforderung-Modell
|
||
status: done
|
||
|
||
Datei: `vergabe_teilnahme/apps/lose/models.py`
|
||
|
||
`Los(FlexibleModel)`:
|
||
- `ausschreibung` FK(Ausschreibung, CASCADE, related_name='lose')
|
||
- `losnummer` CharField(50)
|
||
- `lostitel` CharField(300)
|
||
- `beschreibung` TextField(blank=True)
|
||
- `abgrenzung` TextField(blank=True)
|
||
- `zustaendiger` FK('accounts.Mitarbeiter', null=True, blank=True, SET_NULL)
|
||
- `teilnahme` BooleanField(null=True) # None = offen
|
||
- `status` CharField(50, blank=True)
|
||
- `Meta: ordering = ['losnummer']`
|
||
- `__str__` → `f"Los {self.losnummer}: {self.lostitel}"`
|
||
|
||
`Anforderung(FlexibleModel)`:
|
||
- `ausschreibung` FK(Ausschreibung, CASCADE)
|
||
- `los` FK(Los, null=True, blank=True, SET_NULL, related_name='anforderungen')
|
||
- Alle Felder aus Blueprint 5.3 (titel, beschreibung, quelle_im_dokument, kategorie,
|
||
verbindlichkeit MUSS_CHOICES, ausschlusskriterium, bewertungskriterium,
|
||
zustaendiger FK, erfuellungsstatus CHOICES, nachweis_erforderlich)
|
||
- `dokumente` M2M('dokumente.Dokument', blank=True)
|
||
- `nachweise` M2M('bibliothek.Nachweis', blank=True)
|
||
- `__str__` → `self.titel`
|
||
|
||
Admin für beide Modelle.
|
||
```
|
||
|
||
```task
|
||
id: WP-0002-T06
|
||
title: Aufgaben-App: Aufgabe- und Bieterfrage-Modell
|
||
status: done
|
||
|
||
Datei: `vergabe_teilnahme/apps/aufgaben/models.py`
|
||
|
||
`Aufgabe(FlexibleModel)` mit allen Feldern aus Blueprint 5.4:
|
||
TYP_CHOICES (fachlich/rechtlich/kaufmaennisch/technisch/subunternehmer/dokument/preis)
|
||
STATUS_CHOICES (offen/in_bearbeitung/wartend_intern/wartend_sub/wartend_ausschreiber/erledigt/verworfen/ueberfaellig)
|
||
FKs auf Ausschreibung (CASCADE), Los (null/blank/SET_NULL), Anforderung, Bieterfrage, Dokument.
|
||
prioritaet PositiveSmallIntegerField(default=2, choices=[(1,'Hoch'),(2,'Mittel'),(3,'Niedrig')])
|
||
frist DateField(null=True, blank=True)
|
||
verantwortlicher FK(Mitarbeiter, null=True, SET_NULL)
|
||
ergebnis TextField(blank=True)
|
||
Meta: ordering = ['prioritaet', 'frist']
|
||
|
||
property `ist_ueberfaellig`: frist < date.today() and status not in ['erledigt', 'verworfen']
|
||
|
||
`Bieterfrage(FlexibleModel)` mit allen Feldern aus Blueprint 5.5:
|
||
STATUS_CHOICES (entwurf/abgestimmt/eingereicht/beantwortet/eingearbeitet)
|
||
FKs auf Ausschreibung (CASCADE), Anforderung (null/blank), Dokument (null/blank)
|
||
prioritaet, einreichungsdatum, antwort, auswirkung_angebot, eingearbeitet
|
||
|
||
Admin für beide Modelle.
|
||
```
|
||
|
||
```task
|
||
id: WP-0002-T07
|
||
title: Dokumente-App: Dokument-Modell mit Datei-Upload
|
||
status: done
|
||
|
||
Datei: `vergabe_teilnahme/apps/dokumente/models.py`
|
||
|
||
`Dokument(FlexibleModel)` mit allen Feldern aus Blueprint 5.6:
|
||
- `datei` FileField(upload_to='dokumente/%Y/%m/')
|
||
- `dateiname` CharField(300) — wird beim Upload automatisch aus datei.name befüllt
|
||
- Alle Status-/Kategorie-Choices
|
||
- `ausschreibung` FK(null=True, blank=True)
|
||
- `los` FK(Los, null=True, blank=True)
|
||
- `verantwortlicher`, `pruefer` FK(Mitarbeiter)
|
||
- `finale_abgabeversion` BooleanField(default=False)
|
||
- `upload_datum` DateTimeField(auto_now_add=True)
|
||
- `GenericRelation(Freigabe)` für direkte Freigaben-Abfrage
|
||
|
||
Signal `pre_save`: falls `finale_abgabeversion` von False auf True gesetzt wird,
|
||
setze `status='final_abgegeben'`.
|
||
|
||
Datei-Validierung als Modell-Methode `clean()`:
|
||
Erlaubte MIME-Typen: pdf, docx, xlsx, zip, png, jpg (prüfe gegen Dateiendung, keine MIME-Detection in v1)
|
||
Maximalgröße: settings.MAX_UPLOAD_SIZE (Default: 50 MB)
|
||
|
||
Admin-Registrierung.
|
||
```
|
||
|
||
```task
|
||
id: WP-0002-T08
|
||
title: Preise-App: Preispunkt-Modell
|
||
status: done
|
||
|
||
Datei: `vergabe_teilnahme/apps/preise/models.py`
|
||
|
||
`Preispunkt(FlexibleModel)` gemäß Blueprint 5.7:
|
||
- `ausschreibung` FK(Ausschreibung, CASCADE)
|
||
- `los` FK(Los, null=True, blank=True, SET_NULL)
|
||
- `leistungstyp` CharField(200) — freier Text, z. B. "Schulung", "Lizenz", "Betrieb"
|
||
- `konkrete_leistung` CharField(400)
|
||
- `mengeneinheit`, `waehrung` CharFields mit Defaults
|
||
- `menge`, `einzelpreis`, `gesamtpreis` DecimalFields(14, 2 bzw. 4, null=True, blank=True)
|
||
- `preisstand` DateField(null=True, blank=True)
|
||
- `wiederkehrend` BooleanField(default=False)
|
||
- `laufzeitbezug` CharField(100, blank=True)
|
||
- `subunternehmeranteil` BooleanField(default=False)
|
||
- `subunternehmer` FK('partner.Subunternehmer', null=True, blank=True, SET_NULL)
|
||
- `vergleichsgewicht` DecimalField(3, 1, default=Decimal('1.0'))
|
||
- `gewichtungsbegruendung` TextField(blank=True)
|
||
- `kommentar` TextField(blank=True)
|
||
- `ausschreibung_gewonnen` BooleanField(null=True)
|
||
|
||
Modell-Validierung `clean()`:
|
||
- `vergleichsgewicht` muss zwischen Decimal('0.0') und Decimal('2.0') liegen,
|
||
sonst ValidationError("Vergleichsgewicht muss zwischen 0,0 und 2,0 liegen.")
|
||
|
||
`Meta: ordering = ['leistungstyp', 'konkrete_leistung']`
|
||
|
||
Admin mit list_display=['konkrete_leistung', 'leistungstyp', 'einzelpreis', 'vergleichsgewicht'].
|
||
```
|
||
|
||
```task
|
||
id: WP-0002-T09
|
||
title: Partner-App: Subunternehmer und Dienstleistertyp
|
||
status: done
|
||
|
||
Datei: `vergabe_teilnahme/apps/partner/models.py`
|
||
|
||
`Dienstleistertyp(FlexibleModel)`:
|
||
- name CharField(200), beschreibung TextField(blank=True)
|
||
- typische_leistungen TextField(blank=True), typische_nachweise TextField(blank=True)
|
||
- relevante_standards TextField(blank=True), typische_preisbestandteile TextField(blank=True)
|
||
- bemerkungen TextField(blank=True)
|
||
- `__str__` → name
|
||
|
||
`Subunternehmer(FlexibleModel)`:
|
||
- Alle Felder aus Blueprint 5.9
|
||
- PRAEFERENZ_CHOICES: bevorzugt/zugelassen/gesperrt (default='zugelassen')
|
||
- `dienstleistertyp` FK(Dienstleistertyp, null=True, blank=True, SET_NULL)
|
||
- `bisherige_ausschreibungen` M2M(Ausschreibung, blank=True, through='SubunternehmerZuordnung')
|
||
|
||
`SubunternehmerZuordnung(Model)` — Durchgangstabelle (kein FlexibleModel):
|
||
- `subunternehmer` FK(Subunternehmer, CASCADE)
|
||
- `ausschreibung` FK(Ausschreibung, CASCADE)
|
||
- `los` FK(Los, null=True, blank=True, SET_NULL)
|
||
- `konkrete_leistung` CharField(300, blank=True)
|
||
- `zusage_vorhanden` BooleanField(default=False)
|
||
- `nachweis_eingegangen` BooleanField(default=False)
|
||
- `preis_vorhanden` BooleanField(default=False)
|
||
- `kommentar` TextField(blank=True)
|
||
|
||
Admin für alle drei Modelle.
|
||
```
|
||
|
||
```task
|
||
id: WP-0002-T10
|
||
title: Bibliothek-App: Nachweis, Referenz, Leistungsblatt, Entscheidungsregel
|
||
status: done
|
||
|
||
Datei: `vergabe_teilnahme/apps/bibliothek/models.py`
|
||
|
||
`Nachweis(FlexibleModel)` — Compliance-Nachweise:
|
||
Alle Felder aus Blueprint 6.10 (titel, kurzbeschreibung, dokumenttyp, kategorie,
|
||
datei FileField, version, gueltig_ab/bis DateFields, eigentuemer FK(Mitarbeiter),
|
||
freigabestatus CharField, letzte_pruefung DateField, zugehoerige_standards TextField,
|
||
vertraulichkeit CharField, sprache CharField default='de',
|
||
fuer_oeffentliche BooleanField, fuer_privatwirtschaftliche BooleanField)
|
||
property `ist_abgelaufen`: gueltig_bis < date.today() if gueltig_bis else False
|
||
|
||
`Referenz(FlexibleModel)` — Projekt-Referenzen:
|
||
Alle Felder aus Blueprint 6.11 (referenztitel, kunde, branche, oeffentlich_oder_privat,
|
||
leistungsbeschreibung, eingesetzte_produkte, projektzeitraum CharField,
|
||
vertragsvolumen DecimalField(null=True), ansprechpartner_referenzkunde,
|
||
freigabestatus_verwendung, vertraulichkeit, whitepaper FileField(null=True),
|
||
kurzfassung TextField, langfassung TextField,
|
||
verwendbar_fuer_ausschreibungen BooleanField, einschraenkungen_verwendung TextField)
|
||
leistungsblaetter M2M('Leistungsblatt', blank=True)
|
||
|
||
`Leistungsblatt(FlexibleModel)`:
|
||
produktfunktion, beschreibung, leistungsumfang, grenzen_ausschluesse,
|
||
technische_voraussetzungen, typische_nachweise TextField(blank=True),
|
||
version CharField, eigentuemer FK(Mitarbeiter, null=True)
|
||
|
||
`Entscheidungsregel(FlexibleModel)`:
|
||
regelname, beschreibung, kategorie, gewichtung DecimalField(5,2, default=1.0),
|
||
bewertungslogik TextField, schwellenwert DecimalField(null=True),
|
||
empfehlung CharField(choices=[teilnehmen/nicht_teilnehmen/pruefen]),
|
||
begruendung TextField, gueltig_von/bis DateFields(null=True), aktiv BooleanField(default=True),
|
||
verantwortlicher FK(Mitarbeiter, null=True)
|
||
|
||
Admin für alle vier Modelle.
|
||
```
|
||
|
||
```task
|
||
id: WP-0002-T11
|
||
title: Marktbegleiter-App: Marktbegleiter und Ausschreibungspassage
|
||
status: done
|
||
|
||
Datei: `vergabe_teilnahme/apps/marktbegleiter/models.py`
|
||
|
||
`Marktbegleiter(FlexibleModel)` — alle Felder aus Blueprint 6.15:
|
||
name CharField(300), kurzbeschreibung TextField,
|
||
produkt_leistungsportfolio TextField(blank=True),
|
||
relevante_branchen TextField(blank=True),
|
||
bekannte_staerken TextField(blank=True), bekannte_schwaechen TextField(blank=True),
|
||
typische_formulierungen TextField(blank=True),
|
||
typische_leistungsmerkmale TextField(blank=True),
|
||
bekannte_zertifizierungen TextField(blank=True),
|
||
bekannte_referenzen TextField(blank=True),
|
||
quellen_links TextField(blank=True), letzte_aktualisierung DateField(null=True),
|
||
aktualisierungsstatus CharField(50, blank=True),
|
||
interne_notizen TextField(blank=True),
|
||
vertraulichkeit CharField(20, choices=[intern/streng_vertraulich], default='intern')
|
||
|
||
`Ausschreibungspassage(FlexibleModel)` — alle Felder aus Blueprint 6.16:
|
||
ausschreibung FK(Ausschreibung, CASCADE, related_name='passagen')
|
||
dokument FK(Dokument, null=True, blank=True, SET_NULL)
|
||
fundstelle CharField(200, blank=True)
|
||
passage TextField()
|
||
kategorie CharField(100, blank=True)
|
||
marktbegleiter FK(Marktbegleiter, CASCADE, related_name='passagen')
|
||
begruendung_zuordnung TextField()
|
||
verlaesslichkeitsscore PositiveSmallIntegerField(
|
||
validators=[MinValueValidator(1), MaxValueValidator(10)])
|
||
auswirkung_entscheidung TextField(blank=True)
|
||
auswirkung_preisstrategie TextField(blank=True)
|
||
auswirkung_loesungskonzept TextField(blank=True)
|
||
erfasst_von FK(Mitarbeiter, null=True, SET_NULL)
|
||
erfassungsdatum DateField(auto_now_add=True)
|
||
|
||
Admin für beide Modelle.
|
||
```
|
||
|
||
```task
|
||
id: WP-0002-T12
|
||
title: Nachbetrachtung- und Feedback-Modelle
|
||
status: done
|
||
|
||
Datei: `vergabe_teilnahme/apps/nachbetrachtung/models.py`
|
||
|
||
`Nachbetrachtung(FlexibleModel)`:
|
||
- `ausschreibung` OneToOneField(Ausschreibung, CASCADE, related_name='nachbetrachtung')
|
||
- `ergebnis` CharField(20, choices=[gewonnen/verloren/aufgehoben/offen/zurueckgezogen])
|
||
- `zuschlagsdatum` DateField(null=True, blank=True)
|
||
- `abgegebene_unterlagen` TextField(blank=True)
|
||
- `abgegebene_preise` TextField(blank=True)
|
||
- `verlustgruende` JSONField(default=list)
|
||
# Format: [{"grund": "...", "kategorie": "preis|referenz|...", "verlaesslichkeit": 1-5}]
|
||
- `ausschlaggebende_zuschlagsmerkmale` TextField(blank=True)
|
||
- `lessons_learned` TextField(blank=True)
|
||
- `empfehlungen` TextField(blank=True)
|
||
- `wiederverwendbare_erkenntnisse_markiert` BooleanField(default=False)
|
||
- `projektverantwortlicher` FK(Mitarbeiter, null=True, blank=True, SET_NULL)
|
||
|
||
Datei: `vergabe_teilnahme/apps/feedback/models.py`
|
||
|
||
`Feedbackeintrag(FlexibleModel)` — alle Felder aus Blueprint 6.18:
|
||
titel, beschreibung TextField, seite_kontext CharField(500),
|
||
ausschreibung FK(Ausschreibung, null=True, blank=True, SET_NULL),
|
||
kategorie CharField(choices=[fehler/verbesserung/hinweis]),
|
||
dringlichkeit CharField(choices=[niedrig/mittel/hoch/kritisch], default='mittel'),
|
||
prioritaet PositiveSmallIntegerField(default=2),
|
||
status CharField(choices=[neu/in_bearbeitung/umgesetzt/abgelehnt], default='neu'),
|
||
erfasst_von FK(Mitarbeiter, null=True, SET_NULL),
|
||
datum DateTimeField(auto_now_add=True),
|
||
bewertung TextField(blank=True), entscheidung TextField(blank=True),
|
||
umsetzungshinweis TextField(blank=True)
|
||
|
||
Admin für beide Modelle.
|
||
```
|
||
|
||
```task
|
||
id: WP-0002-T13
|
||
title: Alle Migrationen generieren und ausführen
|
||
status: done
|
||
|
||
Führe aus (in dieser Reihenfolge, da Apps voneinander abhängen):
|
||
```bash
|
||
uv run manage.py makemigrations accounts
|
||
uv run manage.py makemigrations core
|
||
uv run manage.py makemigrations ausschreibungen
|
||
uv run manage.py makemigrations lose
|
||
uv run manage.py makemigrations aufgaben
|
||
uv run manage.py makemigrations dokumente
|
||
uv run manage.py makemigrations preise
|
||
uv run manage.py makemigrations partner
|
||
uv run manage.py makemigrations bibliothek
|
||
uv run manage.py makemigrations marktbegleiter
|
||
uv run manage.py makemigrations nachbetrachtung
|
||
uv run manage.py makemigrations feedback
|
||
uv run manage.py migrate
|
||
```
|
||
|
||
Behebe etwaige Zirkulär-Import-Fehler durch String-Referenzen (z. B.
|
||
`FK('ausschreibungen.Ausschreibung', ...)` statt direktem Import).
|
||
|
||
Prüfe: `uv run manage.py check` → keine Fehler.
|
||
Prüfe: `uv run manage.py showmigrations` → alle Migrationen als [X] markiert.
|
||
```
|
||
|
||
```task
|
||
id: WP-0002-T14
|
||
title: Management-Command für Entwicklungs-Seed-Daten
|
||
status: done
|
||
|
||
Erstelle `vergabe_teilnahme/apps/core/management/commands/seed_dev.py`
|
||
|
||
Der Command erstellt:
|
||
1. 3 Mitarbeiter (BM Max Muster, FV Anna Fach, GF Georg Chef)
|
||
2. 2 Dienstleistertypen (IT-Dienstleister, Rechtsberatung)
|
||
3. 1 Subunternehmer mit Zuordnung zu Dienstleistertyp
|
||
4. 2 Nachweise (ISO-27001 gültig, DSGVO-Muster gültig)
|
||
5. 1 Marktbegleiter
|
||
6. 3 Entscheidungsregeln (Muss-Anforderung nicht erfüllbar, Frist zu kurz, fehlende Referenz)
|
||
7. 1 Ausschreibung (Status 4, mit 2 Losen, 3 Anforderungen, 2 Aufgaben, 2 Preispunkten)
|
||
|
||
Nutze `get_or_create` für alle Objekte damit der Command idempotent ist.
|
||
Ausgabe am Ende: Zusammenfassung der erstellten Objekte.
|
||
|
||
Prüfe: `uv run manage.py seed_dev` läuft fehlerfrei durch.
|
||
Prüfe anschließend: `uv run manage.py shell -c "from vergabe_teilnahme.apps.ausschreibungen.models import Ausschreibung; print(Ausschreibung.objects.count())"` → mindestens 1.
|
||
```
|