from datetime import date, timedelta from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import models from vergabe_teilnahme.apps.core.models import FlexibleModel class Aufgabe(FlexibleModel): TYP_CHOICES = [ ('fachlich', 'Fachlich'), ('rechtlich', 'Rechtlich'), ('kaufmaennisch', 'Kaufmännisch'), ('technisch', 'Technisch'), ('subunternehmer', 'Subunternehmer'), ('dokument', 'Dokument'), ('preis', 'Preis'), ] STATUS_CHOICES = [ ('offen', 'Offen'), ('in_bearbeitung', 'In Bearbeitung'), ('wartend_intern', 'Wartend (intern)'), ('wartend_sub', 'Wartend (Subunternehmer)'), ('wartend_ausschreiber', 'Wartend (Ausschreiber)'), ('erledigt', 'Erledigt'), ('verworfen', 'Verworfen'), ('ueberfaellig', 'Überfällig'), ] PRIORITAET_CHOICES = [(1, 'Hoch'), (2, 'Mittel'), (3, 'Niedrig')] PHASE_CHOICES = [ (1, 'Recherche & Unterlagen'), (2, 'Teilnahmeentscheidung'), (3, 'Detaillierte Durchsicht'), (4, 'Bieterfragen & Klärung'), (5, 'Preismodell'), (6, 'Unterlagen finalisieren'), (7, 'Abgabe'), (8, 'Zuschlag / Nachbetrachtung'), ] ausschreibung = models.ForeignKey( 'ausschreibungen.Ausschreibung', on_delete=models.CASCADE, related_name='aufgaben' ) los = models.ForeignKey( 'lose.Los', on_delete=models.SET_NULL, null=True, blank=True, related_name='aufgaben' ) anforderung = models.ForeignKey( 'lose.Anforderung', on_delete=models.SET_NULL, null=True, blank=True, related_name='aufgaben' ) bieterfrage = models.ForeignKey( 'Bieterfrage', on_delete=models.SET_NULL, null=True, blank=True, related_name='aufgaben' ) dokument = models.ForeignKey( 'dokumente.Dokument', on_delete=models.SET_NULL, null=True, blank=True, related_name='aufgaben' ) titel = models.CharField(max_length=400) beschreibung = models.TextField(blank=True) typ = models.CharField(max_length=20, choices=TYP_CHOICES, default='fachlich') status = models.CharField(max_length=25, choices=STATUS_CHOICES, default='offen') prioritaet = models.PositiveSmallIntegerField(choices=PRIORITAET_CHOICES, default=2) frist = models.DateField(null=True, blank=True) erstellt_am = models.DateTimeField(auto_now_add=True) verantwortlicher = models.ForeignKey( 'accounts.Mitarbeiter', on_delete=models.SET_NULL, null=True, blank=True ) ergebnis = models.TextField(blank=True) phase = models.PositiveSmallIntegerField(choices=PHASE_CHOICES, null=True, blank=True) class Meta: ordering = ['prioritaet', 'frist'] verbose_name = 'Aufgabe' verbose_name_plural = 'Aufgaben' def __str__(self): return self.titel @property def frist_effektiv(self): if self.frist: return self.frist return (self.erstellt_am + timedelta(days=7)).date() @property def ist_ueberfaellig(self): return ( self.frist_effektiv < date.today() and self.status not in ['erledigt', 'verworfen'] ) class Bieterfrage(FlexibleModel): STATUS_CHOICES = [ ('entwurf', 'Entwurf'), ('abgestimmt', 'Abgestimmt'), ('eingereicht', 'Eingereicht'), ('beantwortet', 'Beantwortet'), ('eingearbeitet', 'Eingearbeitet'), ] PRIORITAET_CHOICES = [(1, 'Hoch'), (2, 'Mittel'), (3, 'Niedrig')] ausschreibung = models.ForeignKey( 'ausschreibungen.Ausschreibung', on_delete=models.CASCADE, related_name='bieterfragen' ) anforderung = models.ForeignKey( 'lose.Anforderung', on_delete=models.SET_NULL, null=True, blank=True, related_name='bieterfragen' ) dokument = models.ForeignKey( 'dokumente.Dokument', on_delete=models.SET_NULL, null=True, blank=True, related_name='bieterfragen' ) fragentext = models.TextField() begruendung = models.TextField(blank=True) status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='entwurf') prioritaet = models.PositiveSmallIntegerField(choices=PRIORITAET_CHOICES, default=2) einreichungsdatum = models.DateField(null=True, blank=True) antwort = models.TextField(blank=True) auswirkung_angebot = models.TextField(blank=True) eingearbeitet = models.BooleanField(default=False) verfasser = models.ForeignKey( 'accounts.Mitarbeiter', on_delete=models.SET_NULL, null=True, blank=True ) class Meta: ordering = ['prioritaet', 'status'] verbose_name = 'Bieterfrage' verbose_name_plural = 'Bieterfragen' def __str__(self): return self.fragentext[:80] class AufgabenVerknuepfung(models.Model): aufgabe = models.ForeignKey( Aufgabe, on_delete=models.CASCADE, related_name='verknuepfungen' ) content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() ziel = GenericForeignKey('content_type', 'object_id') kommentar = models.TextField(blank=True) erstellt_am = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['erstellt_am'] verbose_name = 'Aufgaben-Verknüpfung' verbose_name_plural = 'Aufgaben-Verknüpfungen' def __str__(self): return f'{self.aufgabe} → {self.content_type} #{self.object_id}' class ExternalIssue(models.Model): BACKEND_CHOICES = [ ('local', 'Lokal (SQLite)'), ('gitea', 'Gitea'), ] SYNC_STATUS_CHOICES = [ ('open', 'Offen'), ('in_progress', 'In Bearbeitung'), ('blocked', 'Blockiert'), ('closed', 'Geschlossen'), ('error', 'Sync-Fehler'), ] aufgabe = models.OneToOneField( Aufgabe, on_delete=models.CASCADE, related_name='external_issue' ) issue_facade_backend = models.CharField( max_length=20, choices=BACKEND_CHOICES, default='local' ) issue_facade_id = models.CharField( max_length=200, blank=True, help_text='UUID (lokal) oder Issue-Number (Gitea)' ) issue_url = models.URLField(blank=True) issue_key = models.CharField(max_length=100, blank=True, help_text='z.B. "#42" oder "PROJ-1234"') sync_status = models.CharField( max_length=20, choices=SYNC_STATUS_CHOICES, default='open' ) letzter_sync = models.DateTimeField(null=True, blank=True) notizen = models.TextField(blank=True) erstellt_am = models.DateTimeField(auto_now_add=True) class Meta: verbose_name = 'External Issue' verbose_name_plural = 'External Issues' def __str__(self): ident = self.issue_key or (self.issue_facade_id[:8] if self.issue_facade_id else '?') return f'{self.get_issue_facade_backend_display()} #{ident}'