Established workplans
This commit is contained in:
parent
026f7c0db1
commit
315143a6fc
16 changed files with 5492 additions and 0 deletions
503
workplans/WP-0002-fachmodelle.md
Normal file
503
workplans/WP-0002-fachmodelle.md
Normal file
|
|
@ -0,0 +1,503 @@
|
|||
---
|
||||
id: WP-0002
|
||||
title: Fachmodelle — alle Django-Models, Migrationen, Admin
|
||||
status: todo
|
||||
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: todo
|
||||
|
||||
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: todo
|
||||
|
||||
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: todo
|
||||
|
||||
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: todo
|
||||
|
||||
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: todo
|
||||
|
||||
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: todo
|
||||
|
||||
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: todo
|
||||
|
||||
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: todo
|
||||
|
||||
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: todo
|
||||
|
||||
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: todo
|
||||
|
||||
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: todo
|
||||
|
||||
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: todo
|
||||
|
||||
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: todo
|
||||
|
||||
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: todo
|
||||
|
||||
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.
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue